diff --git a/public/app/features/variables/query/QueryVariableEditor.test.tsx b/public/app/features/variables/query/QueryVariableEditor.test.tsx new file mode 100644 index 00000000000..c4df8f2c358 --- /dev/null +++ b/public/app/features/variables/query/QueryVariableEditor.test.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { DataSourceApi } from '@grafana/data'; + +import { Props, QueryVariableEditorUnConnected } from './QueryVariableEditor'; +import { initialQueryVariableModelState } from './reducer'; +import { initialVariableEditorState } from '../editor/reducer'; +import { describe, expect } from '../../../../test/lib/common'; +import { NEW_VARIABLE_ID } from '../state/types'; +import DefaultVariableQueryEditor from '../editor/DefaultVariableQueryEditor'; + +const setupTestContext = (options: Partial) => { + const defaults: Props = { + variable: { ...initialQueryVariableModelState, useTags: true }, + initQueryVariableEditor: jest.fn(), + changeQueryVariableDataSource: jest.fn(), + changeQueryVariableQuery: jest.fn(), + changeVariableMultiValue: jest.fn(), + editor: { + ...initialVariableEditorState, + extended: { + VariableQueryEditor: DefaultVariableQueryEditor, + dataSources: [], + dataSource: ({} as unknown) as DataSourceApi, + }, + }, + onPropChange: jest.fn(), + }; + + const props: Props & Record = { ...defaults, ...options }; + const { rerender } = render(); + + return { rerender, props }; +}; + +describe('QueryVariableEditor', () => { + describe('when the component is mounted', () => { + it('then it should call initQueryVariableEditor', () => { + const { props } = setupTestContext({}); + + expect(props.initQueryVariableEditor).toHaveBeenCalledTimes(1); + expect(props.initQueryVariableEditor).toHaveBeenCalledWith({ type: 'query', id: NEW_VARIABLE_ID }); + }); + }); + + describe('when the user changes', () => { + it.each` + fieldName | propName | expectedArgs + ${'query'} | ${'changeQueryVariableQuery'} | ${[{ type: 'query', id: NEW_VARIABLE_ID }, 't', 't']} + ${'regex'} | ${'onPropChange'} | ${{ propName: 'regex', propValue: 't', updateOptions: true }} + ${'tagsQuery'} | ${'onPropChange'} | ${{ propName: 'tagsQuery', propValue: 't', updateOptions: true }} + ${'tagValuesQuery'} | ${'onPropChange'} | ${{ propName: 'tagValuesQuery', propValue: 't', updateOptions: true }} + `( + '$fieldName field and tabs away then $propName should be called with correct args', + ({ fieldName, propName, expectedArgs }) => { + const { props } = setupTestContext({}); + const propUnderTest = props[propName]; + const fieldAccessor = fieldAccessors[fieldName]; + + userEvent.type(fieldAccessor(), 't'); + userEvent.tab(); + + expect(propUnderTest).toHaveBeenCalledTimes(1); + expect(propUnderTest).toHaveBeenCalledWith(...expectedArgs); + } + ); + }); + + describe('when the user changes', () => { + it.each` + fieldName | propName + ${'query'} | ${'changeQueryVariableQuery'} + ${'regex'} | ${'onPropChange'} + ${'tagsQuery'} | ${'onPropChange'} + ${'tagValuesQuery'} | ${'onPropChange'} + `( + '$fieldName field but reverts the change and tabs away then $propName should not be called', + ({ fieldName, propName }) => { + const { props } = setupTestContext({}); + const propUnderTest = props[propName]; + const fieldAccessor = fieldAccessors[fieldName]; + + userEvent.type(fieldAccessor(), 't'); + userEvent.type(fieldAccessor(), '{backspace}'); + userEvent.tab(); + + expect(propUnderTest).not.toHaveBeenCalled(); + } + ); + }); +}); + +const getQueryField = () => + screen.getByRole('textbox', { name: /variable editor form default variable query editor textarea/i }); + +const getRegExField = () => screen.getByRole('textbox', { name: /variable editor form query regex field/i }); + +const getTagsQueryField = () => screen.getByRole('textbox', { name: /variable editor form query tagsquery field/i }); + +const getTagValuesQueryField = () => + screen.getByRole('textbox', { name: /variable editor form query tagsvaluesquery field/i }); + +const fieldAccessors: Record HTMLElement> = { + query: getQueryField, + regex: getRegExField, + tagsQuery: getTagsQueryField, + tagValuesQuery: getTagValuesQueryField, +}; diff --git a/public/app/features/variables/query/QueryVariableEditor.tsx b/public/app/features/variables/query/QueryVariableEditor.tsx index 127e24b7c4a..c75ed0a7336 100644 --- a/public/app/features/variables/query/QueryVariableEditor.tsx +++ b/public/app/features/variables/query/QueryVariableEditor.tsx @@ -30,7 +30,7 @@ interface DispatchProps { changeVariableMultiValue: typeof changeVariableMultiValue; } -type Props = OwnProps & ConnectedProps & DispatchProps; +export type Props = OwnProps & ConnectedProps & DispatchProps; export interface State { regex: string | null; @@ -73,7 +73,9 @@ export class QueryVariableEditorUnConnected extends PureComponent }; onQueryChange = async (query: any, definition: string) => { - this.props.changeQueryVariableQuery(toVariableIdentifier(this.props.variable), query, definition); + if (this.props.variable.query !== query) { + this.props.changeQueryVariableQuery(toVariableIdentifier(this.props.variable), query, definition); + } }; onRegExChange = (event: ChangeEvent) => { @@ -81,7 +83,10 @@ export class QueryVariableEditorUnConnected extends PureComponent }; onRegExBlur = async (event: ChangeEvent) => { - this.props.onPropChange({ propName: 'regex', propValue: event.target.value, updateOptions: true }); + const regex = event.target.value; + if (this.props.variable.regex !== regex) { + this.props.onPropChange({ propName: 'regex', propValue: regex, updateOptions: true }); + } }; onTagsQueryChange = async (event: ChangeEvent) => { @@ -89,7 +94,10 @@ export class QueryVariableEditorUnConnected extends PureComponent }; onTagsQueryBlur = async (event: ChangeEvent) => { - this.props.onPropChange({ propName: 'tagsQuery', propValue: event.target.value, updateOptions: true }); + const tagsQuery = event.target.value; + if (this.props.variable.tagsQuery !== tagsQuery) { + this.props.onPropChange({ propName: 'tagsQuery', propValue: tagsQuery, updateOptions: true }); + } }; onTagValuesQueryChange = async (event: ChangeEvent) => { @@ -97,7 +105,10 @@ export class QueryVariableEditorUnConnected extends PureComponent }; onTagValuesQueryBlur = async (event: ChangeEvent) => { - this.props.onPropChange({ propName: 'tagValuesQuery', propValue: event.target.value, updateOptions: true }); + const tagValuesQuery = event.target.value; + if (this.props.variable.tagValuesQuery !== tagValuesQuery) { + this.props.onPropChange({ propName: 'tagValuesQuery', propValue: tagValuesQuery, updateOptions: true }); + } }; onRefreshChange = (event: ChangeEvent) => {