Test: Add unit tests for the homepage (#95912)
* test: add skeleton for initial unit tests * test: add basic tests for DataTrailsHome * test-wip: add tests for recent metrics functionality, but need to move from DataTrailsHome.test to DataTrailsRecentMetrics.test * wip: tests for DataTrailBookmarks * test: add tests for recent metrics; refactor: make DataTrailsRecentMetrics accept onSelect as prop rather than whole trail; test: add tests for DataTrailCard (WIP) * test: add test for truncates long list of labels after 3 lines in recent explorations * refactor: make DataTrailBookmarks take in onSelect and onDelete as props rather than a whole trail and onDelete; test: add tests for bookmarks (WIP) * remove deprecated style * fix import issues * fix getTrailForBookmark tests by returning a trail, clean up tests * chore: delete notes to self --------- Co-authored-by: Brendan O'Handley <brendan.ohandley@grafana.com>
This commit is contained in:
co-authored by
Brendan O'Handley
parent
30b3fd2864
commit
c8bc1f8637
@@ -0,0 +1,68 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
|
||||
import { DataTrail } from './DataTrail';
|
||||
import { DataTrailsBookmarks } from './DataTrailBookmarks';
|
||||
import { getTrailStore, DataTrailBookmark } from './TrailStore/TrailStore';
|
||||
|
||||
jest.mock('./TrailStore/TrailStore', () => ({
|
||||
getTrailStore: jest.fn(),
|
||||
getBookmarkKey: jest.fn(() => 'bookmark-key'),
|
||||
}));
|
||||
|
||||
const onSelect = jest.fn();
|
||||
const onDelete = jest.fn();
|
||||
|
||||
describe('DataTrailsBookmarks', () => {
|
||||
const trail = new DataTrail({});
|
||||
const bookmark: DataTrailBookmark = { urlValues: { key: '1', metric: '' }, createdAt: Date.now() };
|
||||
|
||||
beforeEach(() => {
|
||||
onSelect.mockClear();
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [],
|
||||
getTrailForBookmark: jest.fn(),
|
||||
}));
|
||||
});
|
||||
|
||||
it('does not render if there are no bookmarks', () => {
|
||||
render(<DataTrailsBookmarks onSelect={onSelect} onDelete={onDelete} />);
|
||||
expect(screen.queryByText('Or view bookmarks')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the bookmarks header and toggle button', () => {
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [bookmark],
|
||||
recent: [],
|
||||
}));
|
||||
render(<DataTrailsBookmarks onSelect={onSelect} onDelete={onDelete} />);
|
||||
expect(screen.getByText('Or view bookmarks')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('bookmarkCarrot')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('toggles the bookmark list when the toggle button is clicked', () => {
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [bookmark],
|
||||
recent: [],
|
||||
getTrailForBookmark: jest.fn().mockReturnValue(trail),
|
||||
}));
|
||||
render(<DataTrailsBookmarks onSelect={onSelect} onDelete={onDelete} />);
|
||||
const button = screen.getByLabelText('bookmarkCarrot');
|
||||
fireEvent.click(button);
|
||||
expect(screen.getByText('Select metric')).toBeInTheDocument();
|
||||
fireEvent.click(button);
|
||||
expect(screen.queryByText('Select metric')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onDelete when the delete button is clicked', () => {
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [bookmark],
|
||||
recent: [],
|
||||
getTrailForBookmark: jest.fn().mockReturnValue(trail),
|
||||
}));
|
||||
render(<DataTrailsBookmarks onSelect={onSelect} onDelete={onDelete} />);
|
||||
fireEvent.click(screen.getByLabelText('bookmarkCarrot'));
|
||||
fireEvent.click(screen.getByLabelText('Remove bookmark'));
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -2,19 +2,18 @@ import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { SceneComponentProps } from '@grafana/scenes';
|
||||
import { IconButton, useStyles2 } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { DataTrailCard } from './DataTrailCard';
|
||||
import { DataTrailsHome } from './DataTrailsHome';
|
||||
import { getTrailStore, getBookmarkKey } from './TrailStore/TrailStore';
|
||||
|
||||
interface Props extends SceneComponentProps<DataTrailsHome> {
|
||||
type Props = {
|
||||
onSelect: (index: number) => void;
|
||||
onDelete: (index: number) => void;
|
||||
}
|
||||
};
|
||||
|
||||
export function DataTrailsBookmarks({ model, onDelete }: Props) {
|
||||
export function DataTrailsBookmarks({ onSelect, onDelete }: Props) {
|
||||
const [toggleBookmark, setToggleBookmark] = useState(false);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@@ -31,7 +30,7 @@ export function DataTrailsBookmarks({ model, onDelete }: Props) {
|
||||
</div>
|
||||
<IconButton
|
||||
name={toggleBookmark ? 'angle-up' : 'angle-down'}
|
||||
size="xxxl"
|
||||
size="xl"
|
||||
aria-label="bookmarkCarrot"
|
||||
variant="secondary"
|
||||
onClick={() => setToggleBookmark(!toggleBookmark)}
|
||||
@@ -44,7 +43,7 @@ export function DataTrailsBookmarks({ model, onDelete }: Props) {
|
||||
<DataTrailCard
|
||||
key={getBookmarkKey(bookmark)}
|
||||
bookmark={bookmark}
|
||||
onSelect={() => model.onSelectBookmark(index)}
|
||||
onSelect={() => onSelect(index)}
|
||||
onDelete={() => onDelete(index)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
|
||||
import { DataTrail } from './DataTrail';
|
||||
import { DataTrailCard } from './DataTrailCard';
|
||||
import { DataTrailBookmark } from './TrailStore/TrailStore';
|
||||
|
||||
jest.mock('./utils', () => ({
|
||||
...jest.requireActual('./utils'),
|
||||
getDataSource: jest.fn(() => 'Test DataSource'),
|
||||
getDataSourceName: jest.fn(() => 'Test DataSource Name'),
|
||||
}));
|
||||
|
||||
describe('DataTrailCard', () => {
|
||||
// trail is a recent metric exploration
|
||||
const trail = new DataTrail({ key: '1', metric: 'Test Recent Exploration' });
|
||||
// bookmark is a data trail stored in a url
|
||||
const bookmark: DataTrailBookmark = { urlValues: { key: '1', metric: 'Test Bookmark' }, createdAt: Date.now() };
|
||||
const onSelect = jest.fn();
|
||||
const onDelete = jest.fn();
|
||||
beforeEach(() => {
|
||||
onSelect.mockClear();
|
||||
onDelete.mockClear();
|
||||
});
|
||||
|
||||
it('renders the card with recent metric exploration', () => {
|
||||
render(<DataTrailCard trail={trail} onSelect={onSelect} onDelete={onDelete} />);
|
||||
expect(screen.getByText('Test Recent Exploration')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the card with bookmark', () => {
|
||||
render(<DataTrailCard bookmark={bookmark} onSelect={onSelect} onDelete={onDelete} />);
|
||||
expect(screen.getByText('Test Bookmark')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onSelect when the card is clicked', () => {
|
||||
render(<DataTrailCard bookmark={bookmark} onSelect={onSelect} onDelete={onDelete} />);
|
||||
fireEvent.click(screen.getByText('Test Bookmark'));
|
||||
expect(onSelect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onDelete when the delete button is clicked', () => {
|
||||
render(<DataTrailCard bookmark={bookmark} onSelect={onSelect} onDelete={onDelete} />);
|
||||
fireEvent.click(screen.getByTestId('deleteButton'));
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('truncates singular long label in recent explorations', () => {
|
||||
const longLabel =
|
||||
'aajalsdkfaldkjfalskdjfalsdkjfalsdkjflaskjdflaskjdflaskjdflaskjdflasjkdflaskjdflaskjdflaskjflaskdjfldaskjflasjflaskdjflaskjflasjflaskfjalsdfjlskdjflaskjdflajkfjfalkdfjaverylongalskdjlalsjflajkfklsajdfalskjdflkasjdflkadjf';
|
||||
const bookmarkWithLongLabel: DataTrailBookmark = {
|
||||
urlValues: { key: '1', metric: 'metric', 'var-filters': `zone|=|${longLabel}` },
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
render(<DataTrailCard bookmark={bookmarkWithLongLabel} onSelect={onSelect} onDelete={onDelete} />);
|
||||
expect(screen.getByText('...', { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('truncates long list of labels after 3 lines in recent explorations', () => {
|
||||
const bookmarkWithLongLabel: DataTrailBookmark = {
|
||||
urlValues: {
|
||||
key: '1',
|
||||
metric: 'metric',
|
||||
// labels are in a comma separated list
|
||||
'var-filters': `zone|=|averylonglabeltotakeupspace,zone=averylonglabeltotakeupspace,zone1=averylonglabeltotakeupspace,zone2=averylonglabeltotakeupspace,zone3=averylonglabeltotakeupspace,zone4=averylonglabeltotakeupspace`,
|
||||
},
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
render(<DataTrailCard bookmark={bookmarkWithLongLabel} onSelect={onSelect} onDelete={onDelete} />);
|
||||
// to test the non-existence of a truncated label we need queryByText
|
||||
const truncatedLabel = screen.queryByText('zone4');
|
||||
expect(truncatedLabel).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -91,6 +91,7 @@ export function DataTrailCard(props: Props) {
|
||||
className={styles.secondary}
|
||||
tooltip="Remove bookmark"
|
||||
onClick={onDelete}
|
||||
data-testid="deleteButton"
|
||||
/>
|
||||
</Card.SecondaryActions>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import { AdHocFiltersVariable, sceneGraph, SceneObjectRef, SceneVariableSet } from '@grafana/scenes';
|
||||
|
||||
import { DataTrail } from './DataTrail';
|
||||
import { DataTrailsHome } from './DataTrailsHome';
|
||||
import { getTrailStore } from './TrailStore/TrailStore';
|
||||
import { VAR_FILTERS } from './shared';
|
||||
|
||||
jest.mock('./TrailStore/TrailStore', () => ({
|
||||
getTrailStore: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('DataTrailsHome', () => {
|
||||
let scene: DataTrailsHome;
|
||||
beforeEach(() => {
|
||||
const filtersVariable = new AdHocFiltersVariable({ name: VAR_FILTERS });
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [],
|
||||
}));
|
||||
scene = new DataTrailsHome({
|
||||
$variables: new SceneVariableSet({
|
||||
variables: [filtersVariable],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the start button', () => {
|
||||
render(<scene.Component model={scene} />);
|
||||
expect(screen.getByText("Let's start!")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the learn more button and checks its href', () => {
|
||||
render(<scene.Component model={scene} />);
|
||||
const learnMoreButton = screen.getByText('Learn more');
|
||||
expect(learnMoreButton).toBeInTheDocument();
|
||||
expect(learnMoreButton.closest('a')).toHaveAttribute(
|
||||
'href',
|
||||
'https://grafana.com/docs/grafana/latest/explore/explore-metrics/'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not show recent metrics and bookmarks headers for first time user', () => {
|
||||
render(<scene.Component model={scene} />);
|
||||
expect(screen.queryByText('Or view a recent exploration')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Or view bookmarks')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('separator')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('truncates singular long label in recent explorations', () => {
|
||||
const trail = new DataTrail({});
|
||||
function getFilterVar() {
|
||||
const variable = sceneGraph.lookupVariable(VAR_FILTERS, trail);
|
||||
if (variable instanceof AdHocFiltersVariable) {
|
||||
return variable;
|
||||
}
|
||||
throw new Error('getFilterVar failed');
|
||||
}
|
||||
const filtersVariable = getFilterVar();
|
||||
const longLabel = 'averylongalskdjlalsjflajkfklsajdfalskjdflkasjdflkadjf';
|
||||
filtersVariable.setState({
|
||||
filters: [{ key: 'zone', operator: '=', value: longLabel }],
|
||||
});
|
||||
const trailWithResolveMethod = new SceneObjectRef(trail);
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [trailWithResolveMethod],
|
||||
}));
|
||||
render(<scene.Component model={scene} />);
|
||||
expect(screen.getByText('...', { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -88,8 +88,8 @@ export class DataTrailsHome extends SceneObjectBase<DataTrailsHomeState> {
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
<DataTrailsRecentMetrics model={model} />
|
||||
<DataTrailsBookmarks model={model} onDelete={onDelete} />
|
||||
<DataTrailsRecentMetrics onSelect={model.onSelectRecentTrail} />
|
||||
<DataTrailsBookmarks onSelect={model.onSelectBookmark} onDelete={onDelete} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
|
||||
import { SceneObjectRef } from '@grafana/scenes';
|
||||
|
||||
import { DataTrail } from './DataTrail';
|
||||
import { DataTrailsRecentMetrics } from './DataTrailsRecentMetrics';
|
||||
import { getTrailStore } from './TrailStore/TrailStore';
|
||||
|
||||
jest.mock('./TrailStore/TrailStore', () => ({
|
||||
getTrailStore: jest.fn(),
|
||||
}));
|
||||
|
||||
const onSelect = jest.fn();
|
||||
|
||||
describe('DataTrailsRecentMetrics', () => {
|
||||
beforeEach(() => {
|
||||
onSelect.mockClear();
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [],
|
||||
}));
|
||||
});
|
||||
|
||||
it('renders the recent metrics header if there is at least one recent metric', () => {
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [
|
||||
{
|
||||
resolve: () => ({ state: { key: '1' } }),
|
||||
},
|
||||
],
|
||||
}));
|
||||
render(<DataTrailsRecentMetrics onSelect={onSelect} />);
|
||||
expect(screen.getByText('Or view a recent exploration')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show the "Show more" button if there are 3 or fewer recent metrics', () => {
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [
|
||||
{
|
||||
resolve: () => ({ state: { key: '1' } }),
|
||||
},
|
||||
{
|
||||
resolve: () => ({ state: { key: '2' } }),
|
||||
},
|
||||
{
|
||||
resolve: () => ({ state: { key: '3' } }),
|
||||
},
|
||||
],
|
||||
}));
|
||||
render(<DataTrailsRecentMetrics onSelect={onSelect} />);
|
||||
expect(screen.queryByText('Show more')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the "Show more" button if there are more than 3 recent metrics', () => {
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [
|
||||
{
|
||||
resolve: () => ({ state: { key: '1' } }),
|
||||
},
|
||||
{
|
||||
resolve: () => ({ state: { key: '2' } }),
|
||||
},
|
||||
{
|
||||
resolve: () => ({ state: { key: '3' } }),
|
||||
},
|
||||
{
|
||||
resolve: () => ({ state: { key: '4' } }),
|
||||
},
|
||||
],
|
||||
}));
|
||||
render(<DataTrailsRecentMetrics onSelect={onSelect} />);
|
||||
expect(screen.getByText('Show more')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('toggles between "Show more" and "Show less" when the button is clicked', () => {
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [
|
||||
{
|
||||
resolve: () => ({ state: { key: '1' } }),
|
||||
},
|
||||
{
|
||||
resolve: () => ({ state: { key: '2' } }),
|
||||
},
|
||||
{
|
||||
resolve: () => ({ state: { key: '3' } }),
|
||||
},
|
||||
{
|
||||
resolve: () => ({ state: { key: '4' } }),
|
||||
},
|
||||
],
|
||||
}));
|
||||
render(<DataTrailsRecentMetrics onSelect={onSelect} />);
|
||||
const button = screen.getByText('Show more');
|
||||
fireEvent.click(button);
|
||||
expect(screen.getByText('Show less')).toBeInTheDocument();
|
||||
fireEvent.click(button);
|
||||
expect(screen.getByText('Show more')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('selecting a recent exploration card takes you to the metric', () => {
|
||||
const trail = new DataTrail({ key: '1', metric: 'select me' });
|
||||
const trailWithResolveMethod = new SceneObjectRef(trail);
|
||||
(getTrailStore as jest.Mock).mockImplementation(() => ({
|
||||
bookmarks: [],
|
||||
recent: [trailWithResolveMethod],
|
||||
}));
|
||||
render(<DataTrailsRecentMetrics onSelect={onSelect} />);
|
||||
fireEvent.click(screen.getByText('select me'));
|
||||
expect(onSelect).toHaveBeenCalledWith(trail);
|
||||
});
|
||||
});
|
||||
@@ -2,15 +2,16 @@ import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { SceneComponentProps } from '@grafana/scenes';
|
||||
import { Button, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { DataTrail } from './DataTrail';
|
||||
import { DataTrailCard } from './DataTrailCard';
|
||||
import { DataTrailsHome } from './DataTrailsHome';
|
||||
import { getTrailStore } from './TrailStore/TrailStore';
|
||||
|
||||
export function DataTrailsRecentMetrics({ model }: SceneComponentProps<DataTrailsHome>) {
|
||||
type Props = { onSelect: (trail: DataTrail) => void };
|
||||
|
||||
export function DataTrailsRecentMetrics({ onSelect }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const recentMetrics = getTrailStore().recent;
|
||||
const theme = useTheme2();
|
||||
@@ -40,7 +41,7 @@ export function DataTrailsRecentMetrics({ model }: SceneComponentProps<DataTrail
|
||||
<DataTrailCard
|
||||
key={(resolvedTrail.state.key || '') + index}
|
||||
trail={resolvedTrail}
|
||||
onSelect={() => model.onSelectRecentTrail(resolvedTrail)}
|
||||
onSelect={() => onSelect(resolvedTrail)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user