Commit a4b7209e by Ivana Huckova Committed by Arve Knudsen

Rich history: Test coverage (#22852)

* Add unit test coverage

* Add tests to util/richHistory

* Remove unused import

* Remove redundant tests

* Fix tests for components

* Test saving to local storage

* Add boxshadow to container

* Revert "Add boxshadow to container"

This reverts commit 5ca2e850e48377374169956bc98eca05cc8a8102.

* Fix failing tests after merging master

* Fix imports, aria-labels

* Remove console.log

(cherry picked from commit 8edf8e39)
parent 6c001d9c
import {
addToRichHistory,
updateStarredInRichHistory,
updateCommentInRichHistory,
mapNumbertoTimeInSlider,
createDateStringFromTs,
createQueryHeading,
createDataQuery,
deleteAllFromRichHistory,
} from './richHistory';
import store from 'app/core/store';
import { SortOrder } from './explore';
const mock: any = {
history: [
{
comment: '',
datasourceId: 'datasource historyId',
datasourceName: 'datasource history name',
queries: ['query1', 'query2'],
sessionName: '',
starred: true,
ts: 1,
},
],
comment: '',
datasourceId: 'datasourceId',
datasourceName: 'datasourceName',
queries: ['query3'],
sessionName: '',
starred: false,
};
const key = 'grafana.explore.richHistory';
describe('addToRichHistory', () => {
beforeEach(() => {
deleteAllFromRichHistory();
expect(store.exists(key)).toBeFalsy();
});
const expectedResult = [
{
comment: mock.comment,
datasourceId: mock.datasourceId,
datasourceName: mock.datasourceName,
queries: mock.queries,
sessionName: mock.sessionName,
starred: mock.starred,
ts: 2,
},
mock.history[0],
];
it('should append query to query history', () => {
Date.now = jest.fn(() => 2);
const newHistory = addToRichHistory(
mock.history,
mock.datasourceId,
mock.datasourceName,
mock.queries,
mock.starred,
mock.comment,
mock.sessionName
);
expect(newHistory).toEqual(expectedResult);
});
it('should save query history to localStorage', () => {
Date.now = jest.fn(() => 2);
addToRichHistory(
mock.history,
mock.datasourceId,
mock.datasourceName,
mock.queries,
mock.starred,
mock.comment,
mock.sessionName
);
expect(store.exists(key)).toBeTruthy();
expect(store.getObject(key)).toMatchObject(expectedResult);
});
it('should not append duplicated query to query history', () => {
Date.now = jest.fn(() => 2);
const newHistory = addToRichHistory(
mock.history,
mock.history[0].datasourceId,
mock.history[0].datasourceName,
mock.history[0].queries,
mock.starred,
mock.comment,
mock.sessionName
);
expect(newHistory).toEqual([mock.history[0]]);
});
it('should not save duplicated query to localStorage', () => {
Date.now = jest.fn(() => 2);
addToRichHistory(
mock.history,
mock.history[0].datasourceId,
mock.history[0].datasourceName,
mock.history[0].queries,
mock.starred,
mock.comment,
mock.sessionName
);
expect(store.exists(key)).toBeFalsy();
});
});
describe('updateStarredInRichHistory', () => {
it('should update starred in query in history', () => {
const updatedStarred = updateStarredInRichHistory(mock.history, 1);
expect(updatedStarred[0].starred).toEqual(false);
});
it('should update starred in localStorage', () => {
updateStarredInRichHistory(mock.history, 1);
expect(store.exists(key)).toBeTruthy();
expect(store.getObject(key)[0].starred).toEqual(false);
});
});
describe('updateCommentInRichHistory', () => {
it('should update comment in query in history', () => {
const updatedComment = updateCommentInRichHistory(mock.history, 1, 'new comment');
expect(updatedComment[0].comment).toEqual('new comment');
});
it('should update comment in localStorage', () => {
updateCommentInRichHistory(mock.history, 1, 'new comment');
expect(store.exists(key)).toBeTruthy();
expect(store.getObject(key)[0].comment).toEqual('new comment');
});
});
describe('mapNumbertoTimeInSlider', () => {
it('should correctly map number to value', () => {
const value = mapNumbertoTimeInSlider(25);
expect(value).toEqual('25 days ago');
});
});
describe('createDateStringFromTs', () => {
it('should correctly create string value from timestamp', () => {
const value = createDateStringFromTs(1583932327000);
expect(value).toEqual('March 11');
});
});
describe('createQueryHeading', () => {
it('should correctly create heading for queries when sort order is ascending ', () => {
const heading = createQueryHeading(mock.history[0], SortOrder.Ascending);
expect(heading).toEqual('January 1');
});
it('should correctly create heading for queries when sort order is datasourceAZ ', () => {
const heading = createQueryHeading(mock.history[0], SortOrder.DatasourceAZ);
expect(heading).toEqual(mock.history[0].datasourceName);
});
});
describe('createDataQuery', () => {
it('should correctly create data query from rich history query', () => {
const dataQuery = createDataQuery(mock.history[0], mock.queries[0], 0);
expect(dataQuery).toEqual({ datasource: 'datasource history name', expr: 'query3', refId: 'A' });
});
});
import React from 'react';
import { mount } from 'enzyme';
import { GrafanaTheme } from '@grafana/data';
import { ExploreId } from '../../../types/explore';
import { RichHistory, RichHistoryProps } from './RichHistory';
import { Tabs } from './RichHistory';
import { Tab, Slider } from '@grafana/ui';
jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() }));
const setup = (propOverrides?: Partial<RichHistoryProps>) => {
const props: RichHistoryProps = {
theme: {} as GrafanaTheme,
exploreId: ExploreId.left,
height: 100,
activeDatasourceInstance: 'Test datasource',
richHistory: [],
firstTab: Tabs.RichHistory,
deleteRichHistory: jest.fn(),
onClose: jest.fn(),
};
Object.assign(props, propOverrides);
const wrapper = mount(<RichHistory {...props} />);
return wrapper;
};
describe('RichHistory', () => {
it('should render all tabs in tab bar', () => {
const wrapper = setup();
expect(wrapper.find(Tab)).toHaveLength(3);
});
it('should render correct lebels of tabs in tab bar', () => {
const wrapper = setup();
expect(
wrapper
.find(Tab)
.at(0)
.text()
).toEqual('Query history');
expect(
wrapper
.find(Tab)
.at(1)
.text()
).toEqual('Starred');
expect(
wrapper
.find(Tab)
.at(2)
.text()
).toEqual('Settings');
});
it('should correctly render query history tab as active tab', () => {
const wrapper = setup();
expect(wrapper.find(Slider)).toHaveLength(1);
});
it('should correctly render starred tab as active tab', () => {
const wrapper = setup({ firstTab: Tabs.Starred });
expect(wrapper.find(Slider)).toHaveLength(0);
});
});
......@@ -30,7 +30,7 @@ export const sortOrderOptions = [
{ label: 'Data source Z-A', value: SortOrder.DatasourceZA },
];
interface RichHistoryProps extends Themeable {
export interface RichHistoryProps extends Themeable {
richHistory: RichHistoryQuery[];
activeDatasourceInstance: string;
firstTab: Tabs;
......
import React from 'react';
import { mount } from 'enzyme';
import { RichHistoryCard, Props } from './RichHistoryCard';
import { ExploreId } from '../../../types/explore';
import { DataSourceApi } from '@grafana/data';
const setup = (propOverrides?: Partial<Props>) => {
const props: Props = {
query: {
ts: 1,
datasourceName: 'Test datasource',
datasourceId: 'datasource 1',
starred: false,
comment: '',
queries: ['query1', 'query2', 'query3'],
sessionName: '',
},
changeQuery: jest.fn(),
changeDatasource: jest.fn(),
clearQueries: jest.fn(),
updateRichHistory: jest.fn(),
exploreId: ExploreId.left,
datasourceInstance: { name: 'Datasource' } as DataSourceApi,
};
Object.assign(props, propOverrides);
const wrapper = mount(<RichHistoryCard {...props} />);
return wrapper;
};
const starredQueryWithComment = {
ts: 1,
datasourceName: 'Test datasource',
datasourceId: 'datasource 1',
starred: true,
comment: 'test comment',
queries: ['query1', 'query2', 'query3'],
sessionName: '',
};
describe('RichHistoryCard', () => {
it('should render all queries', () => {
const wrapper = setup();
expect(wrapper.find({ 'aria-label': 'Query text' })).toHaveLength(3);
expect(
wrapper
.find({ 'aria-label': 'Query text' })
.at(0)
.text()
).toEqual('query1');
expect(
wrapper
.find({ 'aria-label': 'Query text' })
.at(1)
.text()
).toEqual('query2');
expect(
wrapper
.find({ 'aria-label': 'Query text' })
.at(2)
.text()
).toEqual('query3');
});
describe('commenting', () => {
it('should render comment, if comment present', () => {
const wrapper = setup({ query: starredQueryWithComment });
expect(wrapper.find({ 'aria-label': 'Query comment' })).toHaveLength(1);
expect(wrapper.find({ 'aria-label': 'Query comment' }).text()).toEqual('test comment');
});
it('should have title "Edit comment" at comment icon, if comment present', () => {
const wrapper = setup({ query: starredQueryWithComment });
expect(wrapper.find({ title: 'Edit comment' })).toHaveLength(1);
expect(wrapper.find({ title: 'Add comment' })).toHaveLength(0);
});
it('should have title "Add comment" at comment icon, if no comment present', () => {
const wrapper = setup();
expect(wrapper.find({ title: 'Add comment' })).toHaveLength(1);
expect(wrapper.find({ title: 'Edit comment' })).toHaveLength(0);
});
});
describe('starring', () => {
it('should have title "Star query", if not starred', () => {
const wrapper = setup();
expect(wrapper.find({ title: 'Star query' })).toHaveLength(1);
});
it('should render fa-star-o icon, if not starred', () => {
const wrapper = setup();
expect(wrapper.find({ title: 'Star query' }).hasClass('fa-star-o')).toBe(true);
});
it('should have title "Unstar query", if not starred', () => {
const wrapper = setup({ query: starredQueryWithComment });
expect(wrapper.find({ title: 'Unstar query' })).toHaveLength(1);
});
it('should have fa-star icon, if not starred', () => {
const wrapper = setup({ query: starredQueryWithComment });
expect(wrapper.find({ title: 'Unstar query' }).hasClass('fa-star')).toBe(true);
});
});
});
......@@ -10,7 +10,7 @@ import appEvents from 'app/core/app_events';
import { StoreState } from 'app/types';
import { changeQuery, changeDatasource, clearQueries, updateRichHistory } from '../state/actions';
interface Props {
export interface Props {
query: RichHistoryQuery;
changeQuery: typeof changeQuery;
changeDatasource: typeof changeDatasource;
......@@ -111,12 +111,16 @@ export function RichHistoryCard(props: Props) {
<div className={styles.queryCardLeft} title="Add queries to query editor" onClick={() => onChangeQuery(query)}>
{query.queries.map((q, i) => {
return (
<div key={`${q}-${i}`} className={styles.queryRow}>
<div aria-label="Query text" key={`${q}-${i}`} className={styles.queryRow}>
{q}
</div>
);
})}
{!activeUpdateComment && query.comment && <div className={styles.comment}>{query.comment}</div>}
{!activeUpdateComment && query.comment && (
<div aria-label="Query comment" className={styles.comment}>
{query.comment}
</div>
)}
{activeUpdateComment && (
<div>
<Forms.TextArea
......
import React from 'react';
import { mount } from 'enzyme';
import { Resizable } from 're-resizable';
import { ExploreId } from '../../../types/explore';
import { RichHistoryContainer, Props } from './RichHistoryContainer';
import { Tabs } from './RichHistory';
jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() }));
const setup = (propOverrides?: Partial<Props>) => {
const props: Props = {
width: 500,
exploreId: ExploreId.left,
activeDatasourceInstance: 'Test datasource',
richHistory: [],
firstTab: Tabs.RichHistory,
deleteRichHistory: jest.fn(),
onClose: jest.fn(),
};
Object.assign(props, propOverrides);
const wrapper = mount(<RichHistoryContainer {...props} />);
return wrapper;
};
describe('RichHistoryContainer', () => {
it('should render reseizable component', () => {
const wrapper = setup();
expect(wrapper.find(Resizable)).toHaveLength(1);
});
it('should render component with correct width', () => {
const wrapper = setup();
expect(wrapper.getDOMNode().getAttribute('style')).toContain('width: 531.5px');
});
it('should render component with correct height', () => {
const wrapper = setup();
expect(wrapper.getDOMNode().getAttribute('style')).toContain('height: 400px');
});
});
......@@ -77,7 +77,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
};
});
interface Props {
export interface Props {
width: number;
exploreId: ExploreId;
activeDatasourceInstance: string;
......@@ -87,7 +87,7 @@ interface Props {
onClose: () => void;
}
function RichHistoryContainer(props: Props) {
export function RichHistoryContainer(props: Props) {
const [visible, setVisible] = useState(false);
const [height, setHeight] = useState(400);
......
import React from 'react';
import { mount } from 'enzyme';
import { ExploreId } from '../../../types/explore';
import { SortOrder } from 'app/core/utils/explore';
import { RichHistoryQueriesTab, Props } from './RichHistoryQueriesTab';
import { Slider } from '@grafana/ui';
jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() }));
const setup = (propOverrides?: Partial<Props>) => {
const props: Props = {
queries: [],
sortOrder: SortOrder.Ascending,
activeDatasourceOnly: false,
datasourceFilters: null,
retentionPeriod: 14,
height: 100,
exploreId: ExploreId.left,
onChangeSortOrder: jest.fn(),
onSelectDatasourceFilters: jest.fn(),
};
Object.assign(props, propOverrides);
const wrapper = mount(<RichHistoryQueriesTab {...props} />);
return wrapper;
};
describe('RichHistoryQueriesTab', () => {
describe('slider', () => {
it('should render slider', () => {
const wrapper = setup();
expect(wrapper.find(Slider)).toHaveLength(1);
});
it('should render slider with correct timerange', () => {
const wrapper = setup();
expect(
wrapper
.find('.label-slider')
.at(1)
.text()
).toEqual('today');
expect(
wrapper
.find('.label-slider')
.at(2)
.text()
).toEqual('two weeks ago');
});
});
describe('sort options', () => {
it('should render sorter', () => {
const wrapper = setup();
expect(wrapper.find({ 'aria-label': 'Sort queries' })).toHaveLength(1);
});
});
describe('select datasource', () => {
it('should render select datasource if activeDatasourceOnly is false', () => {
const wrapper = setup();
expect(wrapper.find({ 'aria-label': 'Filter datasources' })).toHaveLength(1);
});
it('should not render select datasource if activeDatasourceOnly is true', () => {
const wrapper = setup({ activeDatasourceOnly: true });
expect(wrapper.find({ 'aria-label': 'Filter datasources' })).toHaveLength(0);
});
});
});
......@@ -23,7 +23,7 @@ import RichHistoryCard from './RichHistoryCard';
import { sortOrderOptions } from './RichHistory';
import { Select, Slider } from '@grafana/ui';
interface Props {
export interface Props {
queries: RichHistoryQuery[];
sortOrder: SortOrder;
activeDatasourceOnly: boolean;
......@@ -190,7 +190,7 @@ export function RichHistoryQueriesTab(props: Props) {
<div className={styles.containerContent}>
<div className={styles.selectors}>
{!activeDatasourceOnly && (
<div className={styles.multiselect}>
<div aria-label="Filter datasources" className={styles.multiselect}>
<Select
isMulti={true}
options={datasources}
......@@ -200,7 +200,7 @@ export function RichHistoryQueriesTab(props: Props) {
/>
</div>
)}
<div className={styles.sort}>
<div aria-label="Sort queries" className={styles.sort}>
<Select
value={sortOrderOptions.filter(order => order.value === sortOrder)}
options={sortOrderOptions}
......
import React from 'react';
import { mount } from 'enzyme';
import { RichHistorySettings, RichHistorySettingsProps } from './RichHistorySettings';
import { Forms } from '@grafana/ui';
const setup = (propOverrides?: Partial<RichHistorySettingsProps>) => {
const props: RichHistorySettingsProps = {
retentionPeriod: 14,
starredTabAsFirstTab: true,
activeDatasourceOnly: false,
onChangeRetentionPeriod: jest.fn(),
toggleStarredTabAsFirstTab: jest.fn(),
toggleactiveDatasourceOnly: jest.fn(),
deleteRichHistory: jest.fn(),
};
Object.assign(props, propOverrides);
const wrapper = mount(<RichHistorySettings {...props} />);
return wrapper;
};
describe('RichHistorySettings', () => {
it('should render component with correct retention period', () => {
const wrapper = setup();
expect(wrapper.find(Forms.Select).text()).toEqual('2 weeks');
});
it('should render component with correctly checked starredTabAsFirstTab settings', () => {
const wrapper = setup();
expect(
wrapper
.find(Forms.Switch)
.at(0)
.prop('value')
).toBe(true);
});
it('should render component with correctly not checked toggleactiveDatasourceOnly settings', () => {
const wrapper = setup();
expect(
wrapper
.find(Forms.Switch)
.at(1)
.prop('value')
).toBe(false);
});
});
......@@ -5,7 +5,7 @@ import { GrafanaTheme, AppEvents } from '@grafana/data';
import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
interface RichHistorySettingsProps {
export interface RichHistorySettingsProps {
retentionPeriod: number;
starredTabAsFirstTab: boolean;
activeDatasourceOnly: boolean;
......
import React from 'react';
import { mount } from 'enzyme';
import { ExploreId } from '../../../types/explore';
import { SortOrder } from 'app/core/utils/explore';
import { RichHistoryStarredTab, Props } from './RichHistoryStarredTab';
jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() }));
const setup = (propOverrides?: Partial<Props>) => {
const props: Props = {
queries: [],
sortOrder: SortOrder.Ascending,
activeDatasourceOnly: false,
datasourceFilters: null,
exploreId: ExploreId.left,
onChangeSortOrder: jest.fn(),
onSelectDatasourceFilters: jest.fn(),
};
Object.assign(props, propOverrides);
const wrapper = mount(<RichHistoryStarredTab {...props} />);
return wrapper;
};
describe('RichHistoryStarredTab', () => {
describe('sorter', () => {
it('should render sorter', () => {
const wrapper = setup();
expect(wrapper.find({ 'aria-label': 'Sort queries' })).toHaveLength(1);
});
});
describe('select datasource', () => {
it('should render select datasource if activeDatasourceOnly is false', () => {
const wrapper = setup();
expect(wrapper.find({ 'aria-label': 'Filter datasources' })).toHaveLength(1);
});
it('should not render select datasource if activeDatasourceOnly is true', () => {
const wrapper = setup({ activeDatasourceOnly: true });
expect(wrapper.find({ 'aria-label': 'Filter datasources' })).toHaveLength(0);
});
});
});
......@@ -18,7 +18,7 @@ import RichHistoryCard from './RichHistoryCard';
import { sortOrderOptions } from './RichHistory';
import { Select } from '@grafana/ui';
interface Props {
export interface Props {
queries: RichHistoryQuery[];
sortOrder: SortOrder;
activeDatasourceOnly: boolean;
......@@ -111,7 +111,7 @@ export function RichHistoryStarredTab(props: Props) {
<div className={styles.containerContent}>
<div className={styles.selectors}>
{!activeDatasourceOnly && (
<div className={styles.multiselect}>
<div aria-label="Filter datasources" className={styles.multiselect}>
<Select
isMulti={true}
options={exploreDatasources}
......@@ -121,7 +121,7 @@ export function RichHistoryStarredTab(props: Props) {
/>
</div>
)}
<div className={styles.sort}>
<div aria-label="Sort queries" className={styles.sort}>
<Select
options={sortOrderOptions}
value={sortOrderOptions.filter(order => order.value === sortOrder)}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment