mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Search: Add unit tests for SearchResultsTable
(#51269)
* initial scaffolding for unit tests * more tests for searchresultstable * skip failing test, remove unused css * Correctly mock stuff so TableCell renders correctly!
This commit is contained in:
parent
3ab410de0b
commit
2429fe1c70
@ -0,0 +1,162 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import {
|
||||
applyFieldOverrides,
|
||||
ArrayVector,
|
||||
createTheme,
|
||||
DataFrame,
|
||||
DataFrameView,
|
||||
FieldType,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { DashboardQueryResult, getGrafanaSearcher, QueryResponse } from '../../service';
|
||||
import { DashboardSearchItemType } from '../../types';
|
||||
|
||||
import { SearchResultsTable } from './SearchResultsTable';
|
||||
|
||||
describe('SearchResultsTable', () => {
|
||||
const mockOnTagSelected = jest.fn();
|
||||
const mockClearSelection = jest.fn();
|
||||
const mockSelectionToggle = jest.fn();
|
||||
const mockSelection = jest.fn();
|
||||
const mockKeyboardEvents = new Subject<React.KeyboardEvent>();
|
||||
|
||||
describe('when there is data', () => {
|
||||
const searchData = toDataFrame({
|
||||
name: 'A',
|
||||
fields: [
|
||||
{ name: 'kind', type: FieldType.string, config: {}, values: [DashboardSearchItemType.DashDB] },
|
||||
{ name: 'uid', type: FieldType.string, config: {}, values: ['my-dashboard-1'] },
|
||||
{ name: 'name', type: FieldType.string, config: {}, values: ['My dashboard 1'] },
|
||||
{ name: 'panel_type', type: FieldType.string, config: {}, values: [''] },
|
||||
{ name: 'url', type: FieldType.string, config: {}, values: ['/my-dashboard-1'] },
|
||||
{ name: 'tags', type: FieldType.other, config: {}, values: [['foo', 'bar']] },
|
||||
{ name: 'ds_uid', type: FieldType.other, config: {}, values: [''] },
|
||||
{ name: 'location', type: FieldType.string, config: {}, values: ['/my-dashboard-1'] },
|
||||
],
|
||||
});
|
||||
const dataFrames = applyFieldOverrides({
|
||||
data: [searchData],
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
replaceVariables: (value, vars, format) => {
|
||||
return vars && value === '${__value.text}' ? vars['__value'].value.text : value;
|
||||
},
|
||||
theme: createTheme(),
|
||||
});
|
||||
|
||||
const mockSearchResult: QueryResponse = {
|
||||
isItemLoaded: jest.fn(),
|
||||
loadMoreItems: jest.fn(),
|
||||
totalRows: searchData.length,
|
||||
view: new DataFrameView<DashboardQueryResult>(dataFrames[0]),
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(getGrafanaSearcher(), 'search').mockResolvedValue(mockSearchResult);
|
||||
});
|
||||
|
||||
it('shows the table with the correct accessible label', () => {
|
||||
render(
|
||||
<SearchResultsTable
|
||||
keyboardEvents={mockKeyboardEvents}
|
||||
response={mockSearchResult}
|
||||
onTagSelected={mockOnTagSelected}
|
||||
selection={mockSelection}
|
||||
selectionToggle={mockSelectionToggle}
|
||||
clearSelection={mockClearSelection}
|
||||
height={1000}
|
||||
width={1000}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole('table', { name: 'Search results table' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has the correct row headers', async () => {
|
||||
render(
|
||||
<SearchResultsTable
|
||||
keyboardEvents={mockKeyboardEvents}
|
||||
response={mockSearchResult}
|
||||
onTagSelected={mockOnTagSelected}
|
||||
selection={mockSelection}
|
||||
selectionToggle={mockSelectionToggle}
|
||||
clearSelection={mockClearSelection}
|
||||
height={1000}
|
||||
width={1000}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole('columnheader', { name: 'Name' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('columnheader', { name: 'Type' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('columnheader', { name: 'Tags' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays the data correctly in the table', () => {
|
||||
render(
|
||||
<SearchResultsTable
|
||||
keyboardEvents={mockKeyboardEvents}
|
||||
response={mockSearchResult}
|
||||
onTagSelected={mockOnTagSelected}
|
||||
selection={mockSelection}
|
||||
selectionToggle={mockSelectionToggle}
|
||||
clearSelection={mockClearSelection}
|
||||
height={1000}
|
||||
width={1000}
|
||||
/>
|
||||
);
|
||||
|
||||
const rows = screen.getAllByRole('row');
|
||||
|
||||
expect(rows).toHaveLength(2);
|
||||
expect(screen.getByText('My dashboard 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('foo')).toBeInTheDocument();
|
||||
expect(screen.getByText('bar')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is no data', () => {
|
||||
const emptySearchData: DataFrame = {
|
||||
fields: [
|
||||
{ name: 'kind', type: FieldType.string, config: {}, values: new ArrayVector([]) },
|
||||
{ name: 'name', type: FieldType.string, config: {}, values: new ArrayVector([]) },
|
||||
{ name: 'uid', type: FieldType.string, config: {}, values: new ArrayVector([]) },
|
||||
{ name: 'url', type: FieldType.string, config: {}, values: new ArrayVector([]) },
|
||||
{ name: 'tags', type: FieldType.other, config: {}, values: new ArrayVector([]) },
|
||||
{ name: 'location', type: FieldType.string, config: {}, values: new ArrayVector([]) },
|
||||
],
|
||||
length: 0,
|
||||
};
|
||||
|
||||
const mockEmptySearchResult: QueryResponse = {
|
||||
isItemLoaded: jest.fn(),
|
||||
loadMoreItems: jest.fn(),
|
||||
totalRows: emptySearchData.length,
|
||||
view: new DataFrameView<DashboardQueryResult>(emptySearchData),
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(getGrafanaSearcher(), 'search').mockResolvedValue(mockEmptySearchResult);
|
||||
});
|
||||
|
||||
it('shows a "No data" message', () => {
|
||||
render(
|
||||
<SearchResultsTable
|
||||
keyboardEvents={mockKeyboardEvents}
|
||||
response={mockEmptySearchResult}
|
||||
onTagSelected={mockOnTagSelected}
|
||||
selection={mockSelection}
|
||||
selectionToggle={mockSelectionToggle}
|
||||
clearSelection={mockClearSelection}
|
||||
height={1000}
|
||||
width={1000}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByRole('table', { name: 'Search results table' })).not.toBeInTheDocument();
|
||||
expect(screen.getByText('No data')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/jsx-no-undef */
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useCallback, useState } from 'react';
|
||||
import { useTable, Column, TableOptions, Cell, useAbsoluteLayout } from 'react-table';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import InfiniteLoader from 'react-window-infinite-loader';
|
||||
@ -50,7 +50,7 @@ export const SearchResultsTable = React.memo(
|
||||
const styles = useStyles2(getStyles);
|
||||
const tableStyles = useStyles2(getTableStyles);
|
||||
const infiniteLoaderRef = useRef<InfiniteLoader>(null);
|
||||
const listRef = useRef<FixedSizeList>(null);
|
||||
const [listEl, setListEl] = useState<FixedSizeList | null>(null);
|
||||
const highlightIndex = useSearchKeyboardNavigation(keyboardEvents, 0, response);
|
||||
|
||||
const memoizedData = useMemo(() => {
|
||||
@ -69,10 +69,10 @@ export const SearchResultsTable = React.memo(
|
||||
if (infiniteLoaderRef.current) {
|
||||
infiniteLoaderRef.current.resetloadMoreItemsCache();
|
||||
}
|
||||
if (listRef.current) {
|
||||
listRef.current.scrollTo(0);
|
||||
if (listEl) {
|
||||
listEl.scrollTo(0);
|
||||
}
|
||||
}, [memoizedData]);
|
||||
}, [memoizedData, listEl]);
|
||||
|
||||
// React-table column definitions
|
||||
const memoizedColumns = useMemo(() => {
|
||||
@ -135,7 +135,7 @@ export const SearchResultsTable = React.memo(
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...getTableProps()} aria-label="Search result table" role="table">
|
||||
<div {...getTableProps()} aria-label="Search results table" role="table">
|
||||
<div>
|
||||
{headerGroups.map((headerGroup) => {
|
||||
const { key, ...headerGroupProps } = headerGroup.getHeaderGroupProps();
|
||||
@ -162,9 +162,12 @@ export const SearchResultsTable = React.memo(
|
||||
itemCount={rows.length}
|
||||
loadMoreItems={response.loadMoreItems}
|
||||
>
|
||||
{({ onItemsRendered }) => (
|
||||
{({ onItemsRendered, ref }) => (
|
||||
<FixedSizeList
|
||||
ref={listRef}
|
||||
ref={(innerRef) => {
|
||||
ref(innerRef);
|
||||
setListEl(innerRef);
|
||||
}}
|
||||
onItemsRendered={onItemsRendered}
|
||||
height={height - HEADER_HEIGHT}
|
||||
itemCount={rows.length}
|
||||
@ -194,27 +197,6 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
`,
|
||||
table: css`
|
||||
width: 100%;
|
||||
`,
|
||||
cellIcon: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`,
|
||||
nameCellStyle: css`
|
||||
border-right: none;
|
||||
padding: ${theme.spacing(1)} ${theme.spacing(1)} ${theme.spacing(1)} ${theme.spacing(2)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: text;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
`,
|
||||
headerNameStyle: css`
|
||||
padding-left: ${theme.spacing(1)};
|
||||
`,
|
||||
headerCell: css`
|
||||
padding: ${theme.spacing(1)};
|
||||
`,
|
||||
@ -239,67 +221,5 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`,
|
||||
typeIcon: css`
|
||||
margin-left: 5px;
|
||||
margin-right: 9.5px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-bottom: ${theme.v1.spacing.xxs};
|
||||
fill: ${theme.colors.text.secondary};
|
||||
`,
|
||||
datasourceItem: css`
|
||||
span {
|
||||
&:hover {
|
||||
color: ${theme.colors.text.link};
|
||||
}
|
||||
}
|
||||
`,
|
||||
missingTitleText: css`
|
||||
color: ${theme.colors.text.disabled};
|
||||
font-style: italic;
|
||||
`,
|
||||
invalidDatasourceItem: css`
|
||||
color: ${theme.colors.error.main};
|
||||
text-decoration: line-through;
|
||||
`,
|
||||
typeText: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
padding-top: ${theme.spacing(1)};
|
||||
`,
|
||||
locationItem: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
margin-right: 12px;
|
||||
`,
|
||||
sortedHeader: css`
|
||||
text-align: right;
|
||||
padding-right: ${theme.spacing(2)};
|
||||
`,
|
||||
sortedItems: css`
|
||||
text-align: right;
|
||||
padding: ${theme.spacing(1)} ${theme.spacing(3)} ${theme.spacing(1)} ${theme.spacing(1)};
|
||||
`,
|
||||
locationCellStyle: css`
|
||||
padding-top: ${theme.spacing(1)};
|
||||
padding-right: ${theme.spacing(1)};
|
||||
`,
|
||||
checkboxHeader: css`
|
||||
margin-left: 2px;
|
||||
`,
|
||||
checkbox: css`
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-top: 5px;
|
||||
`,
|
||||
infoWrap: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
`,
|
||||
tagList: css`
|
||||
padding-top: ${theme.spacing(0.5)};
|
||||
justify-content: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user