actions.test.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. import { locationService } from '@grafana/runtime';
  2. import { reduxTester } from '../../../../../test/core/redux/reduxTester';
  3. import { variableAdapters } from '../../adapters';
  4. import { createQueryVariableAdapter } from '../../query/adapter';
  5. import { queryBuilder } from '../../shared/testing/builders';
  6. import { getPreloadedState, getRootReducer, RootReducerType } from '../../state/helpers';
  7. import { toKeyedAction } from '../../state/keyedVariablesReducer';
  8. import { addVariable, changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer';
  9. import { initialVariableModelState, QueryVariableModel, VariableRefresh, VariableSort } from '../../types';
  10. import { toKeyedVariableIdentifier, toVariablePayload } from '../../utils';
  11. import { NavigationKey } from '../types';
  12. import {
  13. commitChangesToVariable,
  14. filterOrSearchOptions,
  15. navigateOptions,
  16. openOptions,
  17. toggleOptionByHighlight,
  18. } from './actions';
  19. import {
  20. hideOptions,
  21. initialOptionPickerState,
  22. moveOptionsHighlight,
  23. showOptions,
  24. toggleOption,
  25. updateOptionsAndFilter,
  26. updateSearchQuery,
  27. } from './reducer';
  28. const datasource = {
  29. metricFindQuery: jest.fn(() => Promise.resolve([])),
  30. };
  31. jest.mock('@grafana/runtime', () => {
  32. const original = jest.requireActual('@grafana/runtime');
  33. return {
  34. ...original,
  35. getDataSourceSrv: jest.fn(() => ({
  36. get: () => datasource,
  37. })),
  38. locationService: {
  39. partial: jest.fn(),
  40. getSearchObject: () => ({}),
  41. },
  42. };
  43. });
  44. describe('options picker actions', () => {
  45. variableAdapters.setInit(() => [createQueryVariableAdapter()]);
  46. describe('when navigateOptions is dispatched with navigation key cancel', () => {
  47. it('then correct actions are dispatched', async () => {
  48. const variable = createMultiVariable({
  49. options: [createOption('A', 'A', true)],
  50. current: createOption(['A'], ['A'], true),
  51. });
  52. const clearOthers = false;
  53. const key = NavigationKey.cancel;
  54. const tester = await reduxTester<RootReducerType>()
  55. .givenRootReducer(getRootReducer())
  56. .whenActionIsDispatched(
  57. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  58. )
  59. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  60. .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
  61. const option = {
  62. ...createOption(['A']),
  63. selected: true,
  64. value: ['A'],
  65. };
  66. tester.thenDispatchedActionsShouldEqual(
  67. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
  68. toKeyedAction(
  69. 'key',
  70. changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))
  71. ),
  72. toKeyedAction('key', hideOptions())
  73. );
  74. });
  75. });
  76. describe('when navigateOptions is dispatched with navigation key select without clearOthers', () => {
  77. it('then correct actions are dispatched', async () => {
  78. const option = createOption('A', 'A', true);
  79. const variable = createMultiVariable({
  80. options: [option],
  81. current: createOption(['A'], ['A'], true),
  82. includeAll: false,
  83. });
  84. const clearOthers = false;
  85. const key = NavigationKey.select;
  86. const tester = await reduxTester<RootReducerType>()
  87. .givenRootReducer(getRootReducer())
  88. .whenActionIsDispatched(
  89. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  90. )
  91. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  92. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, false))
  93. .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
  94. tester.thenDispatchedActionsShouldEqual(
  95. toKeyedAction('key', toggleOption({ option, forceSelect: false, clearOthers }))
  96. );
  97. });
  98. });
  99. describe('when navigateOptions is dispatched with navigation key select with clearOthers', () => {
  100. it('then correct actions are dispatched', async () => {
  101. const option = createOption('A', 'A', true);
  102. const variable = createMultiVariable({
  103. options: [option],
  104. current: createOption(['A'], ['A'], true),
  105. includeAll: false,
  106. });
  107. const clearOthers = true;
  108. const key = NavigationKey.select;
  109. const tester = await reduxTester<RootReducerType>()
  110. .givenRootReducer(getRootReducer())
  111. .whenActionIsDispatched(
  112. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  113. )
  114. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  115. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  116. .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
  117. tester.thenDispatchedActionsShouldEqual(
  118. toKeyedAction('key', toggleOption({ option, forceSelect: false, clearOthers }))
  119. );
  120. });
  121. });
  122. describe('when navigateOptions is dispatched with navigation key select after highlighting the third option', () => {
  123. it('then correct actions are dispatched', async () => {
  124. const options = [createOption('A'), createOption('B'), createOption('C')];
  125. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  126. const clearOthers = true;
  127. const key = NavigationKey.select;
  128. const tester = await reduxTester<RootReducerType>()
  129. .givenRootReducer(getRootReducer())
  130. .whenActionIsDispatched(
  131. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  132. )
  133. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  134. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  135. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  136. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  137. .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
  138. tester.thenDispatchedActionsShouldEqual(
  139. toKeyedAction('key', toggleOption({ option: options[2], forceSelect: false, clearOthers }))
  140. );
  141. });
  142. });
  143. describe('when navigateOptions is dispatched with navigation key select after highlighting the second option', () => {
  144. it('then correct actions are dispatched', async () => {
  145. const options = [createOption('A'), createOption('B'), createOption('C')];
  146. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  147. const clearOthers = true;
  148. const key = NavigationKey.select;
  149. const tester = await reduxTester<RootReducerType>()
  150. .givenRootReducer(getRootReducer())
  151. .whenActionIsDispatched(
  152. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  153. )
  154. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  155. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  156. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  157. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  158. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveUp, clearOthers))
  159. .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
  160. tester.thenDispatchedActionsShouldEqual(
  161. toKeyedAction('key', toggleOption({ option: options[1], forceSelect: false, clearOthers }))
  162. );
  163. });
  164. });
  165. it('supports having variables with the same label and different values', async () => {
  166. const options = [createOption('sameLabel', 'A'), createOption('sameLabel', 'B')];
  167. const variable = createMultiVariable({
  168. options,
  169. current: createOption(['sameLabel'], ['A'], true),
  170. includeAll: false,
  171. });
  172. const clearOthers = false;
  173. const key = NavigationKey.selectAndClose;
  174. // Open the menu and select the second option
  175. const tester = await reduxTester<RootReducerType>()
  176. .givenRootReducer(getRootReducer())
  177. .whenActionIsDispatched(
  178. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  179. )
  180. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  181. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  182. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  183. .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
  184. const option = createOption(['sameLabel'], ['B'], true);
  185. // Check selecting the second option triggers variables to update
  186. tester.thenDispatchedActionsShouldEqual(
  187. toKeyedAction('key', toggleOption({ option: options[1], forceSelect: true, clearOthers })),
  188. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
  189. toKeyedAction('key', changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))),
  190. toKeyedAction('key', hideOptions()),
  191. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option })))
  192. );
  193. expect(locationService.partial).toHaveBeenLastCalledWith({ 'var-Constant': ['B'] });
  194. });
  195. describe('when navigateOptions is dispatched with navigation key selectAndClose after highlighting the second option', () => {
  196. it('then correct actions are dispatched', async () => {
  197. const options = [createOption('A'), createOption('B'), createOption('C')];
  198. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  199. const clearOthers = false;
  200. const key = NavigationKey.selectAndClose;
  201. const tester = await reduxTester<RootReducerType>()
  202. .givenRootReducer(getRootReducer())
  203. .whenActionIsDispatched(
  204. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  205. )
  206. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  207. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  208. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  209. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  210. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveUp, clearOthers))
  211. .whenAsyncActionIsDispatched(navigateOptions('key', key, clearOthers), true);
  212. const option = {
  213. ...createOption(['B']),
  214. selected: true,
  215. value: ['B'],
  216. };
  217. tester.thenDispatchedActionsShouldEqual(
  218. toKeyedAction('key', toggleOption({ option: options[1], forceSelect: true, clearOthers })),
  219. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
  220. toKeyedAction(
  221. 'key',
  222. changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))
  223. ),
  224. toKeyedAction('key', hideOptions()),
  225. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option })))
  226. );
  227. expect(locationService.partial).toHaveBeenLastCalledWith({ 'var-Constant': ['B'] });
  228. });
  229. });
  230. describe('when filterOrSearchOptions is dispatched with simple filter', () => {
  231. it('then correct actions are dispatched', async () => {
  232. const options = [createOption('A'), createOption('B'), createOption('C')];
  233. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  234. const filter = 'A';
  235. const tester = await reduxTester<RootReducerType>()
  236. .givenRootReducer(getRootReducer())
  237. .whenActionIsDispatched(
  238. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  239. )
  240. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  241. .whenAsyncActionIsDispatched(filterOrSearchOptions(toKeyedVariableIdentifier(variable), filter), true);
  242. tester.thenDispatchedActionsShouldEqual(
  243. toKeyedAction('key', updateSearchQuery(filter)),
  244. toKeyedAction('key', updateOptionsAndFilter(variable.options))
  245. );
  246. });
  247. });
  248. describe('when openOptions is dispatched and there is no picker state yet', () => {
  249. it('then correct actions are dispatched', async () => {
  250. const variable = queryBuilder()
  251. .withId('query0')
  252. .withRootStateKey('key')
  253. .withName('query0')
  254. .withMulti()
  255. .withCurrent(['A', 'C'])
  256. .withOptions('A', 'B', 'C')
  257. .build();
  258. const preloadedState = getPreloadedState('key', {
  259. variables: {
  260. [variable.id]: { ...variable },
  261. },
  262. optionsPicker: { ...initialOptionPickerState },
  263. });
  264. const tester = await reduxTester<RootReducerType>({ preloadedState })
  265. .givenRootReducer(getRootReducer())
  266. .whenAsyncActionIsDispatched(openOptions(toKeyedVariableIdentifier(variable), undefined));
  267. tester.thenDispatchedActionsShouldEqual(toKeyedAction('key', showOptions(variable)));
  268. });
  269. });
  270. describe('when openOptions is dispatched and picker.id is same as variable.id', () => {
  271. it('then correct actions are dispatched', async () => {
  272. const variable = queryBuilder()
  273. .withId('query0')
  274. .withRootStateKey('key')
  275. .withName('query0')
  276. .withMulti()
  277. .withCurrent(['A', 'C'])
  278. .withOptions('A', 'B', 'C')
  279. .build();
  280. const preloadedState = getPreloadedState('key', {
  281. variables: {
  282. [variable.id]: { ...variable },
  283. },
  284. optionsPicker: { ...initialOptionPickerState, id: variable.id },
  285. });
  286. const tester = await reduxTester<RootReducerType>({ preloadedState })
  287. .givenRootReducer(getRootReducer())
  288. .whenAsyncActionIsDispatched(openOptions(toKeyedVariableIdentifier(variable), undefined));
  289. tester.thenDispatchedActionsShouldEqual(toKeyedAction('key', showOptions(variable)));
  290. });
  291. });
  292. describe('when openOptions is dispatched and picker.id is not the same as variable.id', () => {
  293. it('then correct actions are dispatched', async () => {
  294. const variableInPickerState = queryBuilder()
  295. .withId('query1')
  296. .withRootStateKey('key')
  297. .withName('query1')
  298. .withMulti()
  299. .withCurrent(['A', 'C'])
  300. .withOptions('A', 'B', 'C')
  301. .build();
  302. const variable = queryBuilder()
  303. .withId('query0')
  304. .withRootStateKey('key')
  305. .withName('query0')
  306. .withMulti()
  307. .withCurrent(['A'])
  308. .withOptions('A', 'B', 'C')
  309. .build();
  310. const preloadedState = getPreloadedState('key', {
  311. variables: {
  312. [variable.id]: { ...variable },
  313. [variableInPickerState.id]: { ...variableInPickerState },
  314. },
  315. optionsPicker: { ...initialOptionPickerState, id: variableInPickerState.id },
  316. });
  317. const tester = await reduxTester<RootReducerType>({ preloadedState })
  318. .givenRootReducer(getRootReducer())
  319. .whenAsyncActionIsDispatched(openOptions(toKeyedVariableIdentifier(variable), undefined));
  320. tester.thenDispatchedActionsShouldEqual(
  321. toKeyedAction('key', setCurrentVariableValue({ type: 'query', id: 'query1', data: { option: undefined } })),
  322. toKeyedAction(
  323. 'key',
  324. changeVariableProp({ type: 'query', id: 'query1', data: { propName: 'queryValue', propValue: '' } })
  325. ),
  326. toKeyedAction('key', hideOptions()),
  327. toKeyedAction('key', showOptions(variable))
  328. );
  329. });
  330. });
  331. describe('when commitChangesToVariable is dispatched with no changes', () => {
  332. it('then correct actions are dispatched', async () => {
  333. const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
  334. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  335. const tester = await reduxTester<RootReducerType>()
  336. .givenRootReducer(getRootReducer())
  337. .whenActionIsDispatched(
  338. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  339. )
  340. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  341. .whenAsyncActionIsDispatched(commitChangesToVariable('key'), true);
  342. const option = {
  343. ...createOption(['A']),
  344. selected: true,
  345. value: ['A'] as any[],
  346. };
  347. tester.thenDispatchedActionsShouldEqual(
  348. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
  349. toKeyedAction(
  350. 'key',
  351. changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))
  352. ),
  353. toKeyedAction('key', hideOptions())
  354. );
  355. });
  356. });
  357. describe('when commitChangesToVariable is dispatched with changes', () => {
  358. it('then correct actions are dispatched', async () => {
  359. const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
  360. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  361. const clearOthers = false;
  362. const tester = await reduxTester<RootReducerType>()
  363. .givenRootReducer(getRootReducer())
  364. .whenActionIsDispatched(
  365. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  366. )
  367. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  368. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  369. .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers))
  370. .whenAsyncActionIsDispatched(commitChangesToVariable('key'), true);
  371. const option = {
  372. ...createOption([]),
  373. selected: true,
  374. value: [],
  375. };
  376. tester.thenDispatchedActionsShouldEqual(
  377. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
  378. toKeyedAction(
  379. 'key',
  380. changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: '' }))
  381. ),
  382. toKeyedAction('key', hideOptions()),
  383. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option })))
  384. );
  385. expect(locationService.partial).toHaveBeenLastCalledWith({ 'var-Constant': [] });
  386. });
  387. });
  388. describe('when commitChangesToVariable is dispatched with changes and list of options is filtered', () => {
  389. it('then correct actions are dispatched', async () => {
  390. const options = [createOption('A', 'A', true), createOption('B'), createOption('C')];
  391. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  392. const clearOthers = false;
  393. const tester = await reduxTester<RootReducerType>()
  394. .givenRootReducer(getRootReducer())
  395. .whenActionIsDispatched(
  396. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  397. )
  398. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  399. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  400. .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers))
  401. .whenActionIsDispatched(filterOrSearchOptions(toKeyedVariableIdentifier(variable), 'C'))
  402. .whenAsyncActionIsDispatched(commitChangesToVariable('key'), true);
  403. const option = {
  404. ...createOption([]),
  405. selected: true,
  406. value: [],
  407. };
  408. tester.thenDispatchedActionsShouldEqual(
  409. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option }))),
  410. toKeyedAction(
  411. 'key',
  412. changeVariableProp(toVariablePayload(variable, { propName: 'queryValue', propValue: 'C' }))
  413. ),
  414. toKeyedAction('key', hideOptions()),
  415. toKeyedAction('key', setCurrentVariableValue(toVariablePayload(variable, { option })))
  416. );
  417. expect(locationService.partial).toHaveBeenLastCalledWith({ 'var-Constant': [] });
  418. });
  419. });
  420. describe('when toggleOptionByHighlight is dispatched with changes', () => {
  421. it('then correct actions are dispatched', async () => {
  422. const options = [createOption('A'), createOption('B'), createOption('C')];
  423. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  424. const clearOthers = false;
  425. const tester = await reduxTester<RootReducerType>()
  426. .givenRootReducer(getRootReducer())
  427. .whenActionIsDispatched(
  428. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  429. )
  430. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  431. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  432. .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers), true);
  433. const option = createOption('A');
  434. tester.thenDispatchedActionsShouldEqual(
  435. toKeyedAction('key', toggleOption({ option, forceSelect: false, clearOthers }))
  436. );
  437. });
  438. });
  439. describe('when toggleOptionByHighlight is dispatched with changes selected from a filtered options list', () => {
  440. it('then correct actions are dispatched', async () => {
  441. const options = [createOption('A'), createOption('B'), createOption('BC'), createOption('BD')];
  442. const variable = createMultiVariable({ options, current: createOption(['A'], ['A'], true), includeAll: false });
  443. const clearOthers = false;
  444. const tester = await reduxTester<RootReducerType>()
  445. .givenRootReducer(getRootReducer())
  446. .whenActionIsDispatched(
  447. toKeyedAction('key', addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable })))
  448. )
  449. .whenActionIsDispatched(toKeyedAction('key', showOptions(variable)))
  450. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  451. .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers), true)
  452. .whenActionIsDispatched(filterOrSearchOptions(toKeyedVariableIdentifier(variable), 'B'))
  453. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  454. .whenActionIsDispatched(navigateOptions('key', NavigationKey.moveDown, clearOthers))
  455. .whenActionIsDispatched(toggleOptionByHighlight('key', clearOthers));
  456. const optionA = createOption('A');
  457. const optionBC = createOption('BD');
  458. tester.thenDispatchedActionsShouldEqual(
  459. toKeyedAction('key', toggleOption({ option: optionA, forceSelect: false, clearOthers })),
  460. toKeyedAction('key', updateSearchQuery('B')),
  461. toKeyedAction('key', updateOptionsAndFilter(variable.options)),
  462. toKeyedAction('key', moveOptionsHighlight(1)),
  463. toKeyedAction('key', moveOptionsHighlight(1)),
  464. toKeyedAction('key', toggleOption({ option: optionBC, forceSelect: false, clearOthers }))
  465. );
  466. });
  467. });
  468. });
  469. function createMultiVariable(extend?: Partial<QueryVariableModel>): QueryVariableModel {
  470. return {
  471. ...initialVariableModelState,
  472. type: 'query',
  473. id: '0',
  474. rootStateKey: 'key',
  475. index: 0,
  476. current: createOption([]),
  477. options: [],
  478. query: 'options-query',
  479. name: 'Constant',
  480. datasource: { uid: 'datasource' },
  481. definition: '',
  482. sort: VariableSort.alphabeticalAsc,
  483. refresh: VariableRefresh.never,
  484. regex: '',
  485. multi: true,
  486. includeAll: true,
  487. ...(extend ?? {}),
  488. };
  489. }
  490. function createOption(text: string | string[], value?: string | string[], selected?: boolean) {
  491. const metric = createMetric(text);
  492. return {
  493. ...metric,
  494. value: value ?? metric.value,
  495. selected: selected ?? false,
  496. };
  497. }
  498. function createMetric(value: string | string[]) {
  499. return {
  500. value: value,
  501. text: value,
  502. };
  503. }