123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- import { locationService } from '@grafana/runtime';
- import { reduxTester } from '../../../../../test/core/redux/reduxTester';
- import { variableAdapters } from '../../adapters';
- import { createQueryVariableAdapter } from '../../query/adapter';
- import { queryBuilder } from '../../shared/testing/builders';
- import { getPreloadedState, getRootReducer, RootReducerType } from '../../state/helpers';
- import { toKeyedAction } from '../../state/keyedVariablesReducer';
- import { addVariable, changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer';
- import { initialVariableModelState, QueryVariableModel, VariableRefresh, VariableSort } from '../../types';
- import { toKeyedVariableIdentifier, toVariablePayload } from '../../utils';
- import { NavigationKey } from '../types';
- import {
- commitChangesToVariable,
- filterOrSearchOptions,
- navigateOptions,
- openOptions,
- toggleOptionByHighlight,
- } from './actions';
- import {
- hideOptions,
- initialOptionPickerState,
- moveOptionsHighlight,
- showOptions,
- toggleOption,
- updateOptionsAndFilter,
- updateSearchQuery,
- } from './reducer';
- const datasource = {
- metricFindQuery: jest.fn(() => Promise.resolve([])),
- };
- jest.mock('@grafana/runtime', () => {
- const original = jest.requireActual('@grafana/runtime');
- return {
- ...original,
- getDataSourceSrv: jest.fn(() => ({
- get: () => datasource,
- })),
- locationService: {
- partial: jest.fn(),
- getSearchObject: () => ({}),
- },
- };
- });
- describe('options picker actions', () => {
- variableAdapters.setInit(() => [createQueryVariableAdapter()]);
- describe('when navigateOptions is dispatched with navigation key cancel', () => {
- it('then correct actions are dispatched', async () => {
- const variable = createMultiVariable({
- options: [createOption('A', 'A', true)],
- current: createOption(['A'], ['A'], true),
- });
- const clearOthers = false;
- const key = NavigationKey.cancel;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
- const option = {
- ...createOption(['A']),
- selected: true,
- value: ['A'],
- };
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
- toKeyedAction(
- 'key',
- changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))
- ),
- toKeyedAction('key', hideOptions())
- );
- });
- });
- describe('when navigateOptions is dispatched with navigation key select without clearOthers', () => {
- it('then correct actions are dispatched', async () => {
- const option = createOption('A', 'A', true);
- const variable = createMultiVariable({
- options: [option],
- current: createOption(['A'], ['A'], true),
- includeAll: false,
- });
- const clearOthers = false;
- const key = NavigationKey.select;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, false))
- .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', toggleOption({ option, forceSelect: false, clearOthers }))
- );
- });
- });
- describe('when navigateOptions is dispatched with navigation key select with clearOthers', () => {
- it('then correct actions are dispatched', async () => {
- const option = createOption('A', 'A', true);
- const variable = createMultiVariable({
- options: [option],
- current: createOption(['A'], ['A'], true),
- includeAll: false,
- });
- const clearOthers = true;
- const key = NavigationKey.select;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', toggleOption({ option, forceSelect: false, clearOthers }))
- );
- });
- });
- describe('when navigateOptions is dispatched with navigation key select after highlighting the third option', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A'), createOption('B'), createOption('C')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const clearOthers = true;
- const key = NavigationKey.select;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', toggleOption({ option: options[2], forceSelect: false, clearOthers }))
- );
- });
- });
- describe('when navigateOptions is dispatched with navigation key select after highlighting the second option', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A'), createOption('B'), createOption('C')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const clearOthers = true;
- const key = NavigationKey.select;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveUp, clearOthers))
- .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', toggleOption({ option: options[1], forceSelect: false, clearOthers }))
- );
- });
- });
- it('supports having variables with the same label and different values', async () => {
- const options = [createOption('sameLabel', 'A'), createOption('sameLabel', 'B')];
- const variable = createMultiVariable({
- options,
- current: createOption(['sameLabel'], ['A'], true),
- includeAll: false,
- });
- const clearOthers = false;
- const key = NavigationKey.selectAndClose;
- // Open the menu and select the second option
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
- const option = createOption(['sameLabel'], ['B'], true);
- // Check selecting the second option triggers variables to update
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', toggleOption({ option: options[1], forceSelect: true, clearOthers })),
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
- toKeyedAction('key', changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))),
- toKeyedAction('key', hideOptions()),
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option })))
- );
- expect(locationService.partial).toHaveBeenLastCalledWith({ 'var-Constant': ['B'] });
- });
- describe('when navigateOptions is dispatched with navigation key selectAndClose after highlighting the second option', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A'), createOption('B'), createOption('C')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const clearOthers = false;
- const key = NavigationKey.selectAndClose;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveUp, clearOthers))
- .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
- const option = {
- ...createOption(['B']),
- selected: true,
- value: ['B'],
- };
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', toggleOption({ option: options[1], forceSelect: true, clearOthers })),
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
- toKeyedAction(
- 'key',
- changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))
- ),
- toKeyedAction('key', hideOptions()),
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option })))
- );
- expect(locationService.partial).toHaveBeenLastCalledWith({ 'var-Constant': ['B'] });
- });
- });
- describe('when filterOrSearchOptions is dispatched with simple filter', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A'), createOption('B'), createOption('C')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const filter = 'A';
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenAsyncActionIsDispatched(filterOrSearchOptions(toKeyedVariableIdentifier(variable), filter), true);
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', updateSearchQuery(filter)),
- toKeyedAction('key', updateOptionsAndFilter(variable.options))
- );
- });
- });
- describe('when openOptions is dispatched and there is no picker state yet', () => {
- it('then correct actions are dispatched', async () => {
- const variable = queryBuilder()
- .withId('query0')
- .withRootStateKey('key')
- .withName('query0')
- .withMulti()
- .withCurrent(['A', 'C'])
- .withOptions('A', 'B', 'C')
- .build();
- const preloadedState = getPreloadedState('key', {
- variables: {
- [variable.id]: { ...variable },
- },
- optionsPicker: { ...initialOptionPickerState },
- });
- const tester = await reduxTester<RootReducerType>({ preloadedState })
- .givenRootReducer(getRootReducer())
- .whenAsyncActionIsDispatched(openOptions(toKeyedVariableIdentifier(variable), undefined));
- tester.thenDispatchedActionsShouldEqual(toKeyedAction('key', showOptions(variable)));
- });
- });
- describe('when openOptions is dispatched and picker.id is same as variable.id', () => {
- it('then correct actions are dispatched', async () => {
- const variable = queryBuilder()
- .withId('query0')
- .withRootStateKey('key')
- .withName('query0')
- .withMulti()
- .withCurrent(['A', 'C'])
- .withOptions('A', 'B', 'C')
- .build();
- const preloadedState = getPreloadedState('key', {
- variables: {
- [variable.id]: { ...variable },
- },
- optionsPicker: { ...initialOptionPickerState, id: variable.id },
- });
- const tester = await reduxTester<RootReducerType>({ preloadedState })
- .givenRootReducer(getRootReducer())
- .whenAsyncActionIsDispatched(openOptions(toKeyedVariableIdentifier(variable), undefined));
- tester.thenDispatchedActionsShouldEqual(toKeyedAction('key', showOptions(variable)));
- });
- });
- describe('when openOptions is dispatched and picker.id is not the same as variable.id', () => {
- it('then correct actions are dispatched', async () => {
- const variableInPickerState = queryBuilder()
- .withId('query1')
- .withRootStateKey('key')
- .withName('query1')
- .withMulti()
- .withCurrent(['A', 'C'])
- .withOptions('A', 'B', 'C')
- .build();
- const variable = queryBuilder()
- .withId('query0')
- .withRootStateKey('key')
- .withName('query0')
- .withMulti()
- .withCurrent(['A'])
- .withOptions('A', 'B', 'C')
- .build();
- const preloadedState = getPreloadedState('key', {
- variables: {
- [variable.id]: { ...variable },
- [variableInPickerState.id]: { ...variableInPickerState },
- },
- optionsPicker: { ...initialOptionPickerState, id: variableInPickerState.id },
- });
- const tester = await reduxTester<RootReducerType>({ preloadedState })
- .givenRootReducer(getRootReducer())
- .whenAsyncActionIsDispatched(openOptions(toKeyedVariableIdentifier(variable), undefined));
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', setCurrentVariableValue({ type: 'query', id: 'query1', data: { option: undefined } })),
- toKeyedAction(
- 'key',
- changeVariableProp({ type: 'query', id: 'query1', data: { propName: 'queryValue', propValue: '' } })
- ),
- toKeyedAction('key', hideOptions()),
- toKeyedAction('key', showOptions(variable))
- );
- });
- });
- describe('when commitChangesToVariable is dispatched with no changes', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenAsyncActionIsDispatched(commitChangesToVariable('key'), true);
- const option = {
- ...createOption(['A']),
- selected: true,
- value: ['A'] as any[],
- };
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
- toKeyedAction(
- 'key',
- changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))
- ),
- toKeyedAction('key', hideOptions())
- );
- });
- });
- describe('when commitChangesToVariable is dispatched with changes', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const clearOthers = false;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers))
- .whenAsyncActionIsDispatched(commitChangesToVariable('key'), true);
- const option = {
- ...createOption([]),
- selected: true,
- value: [],
- };
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
- toKeyedAction(
- 'key',
- changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))
- ),
- toKeyedAction('key', hideOptions()),
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option })))
- );
- expect(locationService.partial).toHaveBeenLastCalledWith({ 'var-Constant': [] });
- });
- });
- describe('when commitChangesToVariable is dispatched with changes and list of options is filtered', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const clearOthers = false;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers))
- .whenActionIsDispatched(filterOrSearchOptions(toKeyedVariableIdentifier(variable), 'C'))
- .whenAsyncActionIsDispatched(commitChangesToVariable('key'), true);
- const option = {
- ...createOption([]),
- selected: true,
- value: [],
- };
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
- toKeyedAction(
- 'key',
- changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: 'C' }))
- ),
- toKeyedAction('key', hideOptions()),
- toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option })))
- );
- expect(locationService.partial).toHaveBeenLastCalledWith({ 'var-Constant': [] });
- });
- });
- describe('when toggleOptionByHighlight is dispatched with changes', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A'), createOption('B'), createOption('C')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const clearOthers = false;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers), true);
- const option = createOption('A');
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', toggleOption({ option, forceSelect: false, clearOthers }))
- );
- });
- });
- describe('when toggleOptionByHighlight is dispatched with changes selected from a filtered options list', () => {
- it('then correct actions are dispatched', async () => {
- const options = [createOption('A'), createOption('B'), createOption('BC'), createOption('BD')];
- const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
- const clearOthers = false;
- const tester = await reduxTester<RootReducerType>()
- .givenRootReducer(getRootReducer())
- .whenActionIsDispatched(
- toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
- )
- .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers), true)
- .whenActionIsDispatched(filterOrSearchOptions(toKeyedVariableIdentifier(variable), 'B'))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
- .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers));
- const optionA = createOption('A');
- const optionBC = createOption('BD');
- tester.thenDispatchedActionsShouldEqual(
- toKeyedAction('key', toggleOption({ option: optionA, forceSelect: false, clearOthers })),
- toKeyedAction('key', updateSearchQuery('B')),
- toKeyedAction('key', updateOptionsAndFilter(variable.options)),
- toKeyedAction('key', moveOptionsHighlight(1)),
- toKeyedAction('key', moveOptionsHighlight(1)),
- toKeyedAction('key', toggleOption({ option: optionBC, forceSelect: false, clearOthers }))
- );
- });
- });
- });
- function createMultiVariable(extend?: Partial<QueryVariableModel>): QueryVariableModel {
- return {
- ...initialVariableModelState,
- type: 'query',
- id: '0',
- rootStateKey: 'key',
- index: 0,
- current: createOption([]),
- options: [],
- query: 'options-query',
- name: 'Constant',
- datasource: { uid: 'datasource' },
- definition: '',
- sort: VariableSort.alphabeticalAsc,
- refresh: VariableRefresh.never,
- regex: '',
- multi: true,
- includeAll: true,
- ...(extend ?? {}),
- };
- }
- function createOption(text: string | string[], value?: string | string[], selected?: boolean) {
- const metric = createMetric(text);
- return {
- ...metric,
- value: value ?? metric.value,
- selected: selected ?? false,
- };
- }
- function createMetric(value: string | string[]) {
- return {
- value: value,
- text: value,
- };
- }
|