OptionsPaneOptions.test.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import { fireEvent, render, screen, within } from '@testing-library/react';
  2. import React from 'react';
  3. import { Provider } from 'react-redux';
  4. import configureMockStore from 'redux-mock-store';
  5. import {
  6. FieldConfigSource,
  7. FieldType,
  8. LoadingState,
  9. PanelData,
  10. standardEditorsRegistry,
  11. standardFieldConfigEditorRegistry,
  12. toDataFrame,
  13. } from '@grafana/data';
  14. import { selectors } from '@grafana/e2e-selectors';
  15. import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/editors/registry';
  16. import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
  17. import { DashboardModel, PanelModel } from '../../state';
  18. import { OptionsPaneOptions } from './OptionsPaneOptions';
  19. import { dataOverrideTooltipDescription, overrideRuleTooltipDescription } from './state/getOptionOverrides';
  20. standardEditorsRegistry.setInit(getAllOptionEditors);
  21. standardFieldConfigEditorRegistry.setInit(getAllStandardFieldConfigs);
  22. const mockStore = configureMockStore<any, any>();
  23. const OptionsPaneSelector = selectors.components.PanelEditor.OptionsPane;
  24. jest.mock('react-router-dom', () => ({
  25. ...(jest.requireActual('react-router-dom') as any),
  26. useLocation: () => ({
  27. pathname: 'localhost:3000/example/path',
  28. }),
  29. }));
  30. class OptionsPaneOptionsTestScenario {
  31. onFieldConfigsChange = jest.fn();
  32. onPanelOptionsChanged = jest.fn();
  33. onPanelConfigChange = jest.fn();
  34. panelData: PanelData = {
  35. series: [],
  36. state: LoadingState.Done,
  37. timeRange: {} as any,
  38. };
  39. plugin = getPanelPlugin({
  40. id: 'TestPanel',
  41. }).useFieldConfig({
  42. standardOptions: {},
  43. useCustomConfig: (b) => {
  44. b.addBooleanSwitch({
  45. name: 'CustomBool',
  46. path: 'CustomBool',
  47. })
  48. .addBooleanSwitch({
  49. name: 'HiddenFromDef',
  50. path: 'HiddenFromDef',
  51. hideFromDefaults: true,
  52. })
  53. .addTextInput({
  54. name: 'TextPropWithCategory',
  55. path: 'TextPropWithCategory',
  56. settings: {
  57. placeholder: 'CustomTextPropPlaceholder',
  58. },
  59. category: ['Axis'],
  60. });
  61. },
  62. });
  63. panel = new PanelModel({
  64. title: 'Test title',
  65. type: this.plugin.meta.id,
  66. fieldConfig: {
  67. defaults: {
  68. max: 100,
  69. thresholds: {
  70. mode: 'absolute',
  71. steps: [
  72. { value: -Infinity, color: 'green' },
  73. { value: 100, color: 'green' },
  74. ],
  75. },
  76. },
  77. overrides: [],
  78. },
  79. options: {},
  80. });
  81. dashboard = new DashboardModel({});
  82. store = mockStore({
  83. dashboard: { panels: [] },
  84. templating: {
  85. variables: {},
  86. },
  87. });
  88. render() {
  89. render(
  90. <Provider store={this.store}>
  91. <OptionsPaneOptions
  92. data={this.panelData}
  93. plugin={this.plugin}
  94. panel={this.panel}
  95. dashboard={this.dashboard}
  96. onFieldConfigsChange={this.onFieldConfigsChange}
  97. onPanelConfigChange={this.onPanelConfigChange}
  98. onPanelOptionsChanged={this.onPanelOptionsChanged}
  99. instanceState={undefined}
  100. />
  101. </Provider>
  102. );
  103. }
  104. }
  105. describe('OptionsPaneOptions', () => {
  106. it('should render panel frame options', async () => {
  107. const scenario = new OptionsPaneOptionsTestScenario();
  108. scenario.render();
  109. expect(screen.getByLabelText(OptionsPaneSelector.fieldLabel('Panel options Title'))).toBeInTheDocument();
  110. });
  111. it('should render all categories', async () => {
  112. const scenario = new OptionsPaneOptionsTestScenario();
  113. scenario.render();
  114. expect(screen.getByRole('heading', { name: /Panel options/ })).toBeInTheDocument();
  115. expect(screen.getByRole('heading', { name: /Standard options/ })).toBeInTheDocument();
  116. expect(screen.getByRole('heading', { name: /Value mappings/ })).toBeInTheDocument();
  117. expect(screen.getByRole('heading', { name: /Thresholds/ })).toBeInTheDocument();
  118. expect(screen.getByRole('heading', { name: /TestPanel/ })).toBeInTheDocument();
  119. });
  120. it('should render custom options', () => {
  121. const scenario = new OptionsPaneOptionsTestScenario();
  122. scenario.render();
  123. expect(screen.getByLabelText(OptionsPaneSelector.fieldLabel('TestPanel CustomBool'))).toBeInTheDocument();
  124. });
  125. it('should not render options that are marked as hidden from defaults', () => {
  126. const scenario = new OptionsPaneOptionsTestScenario();
  127. scenario.render();
  128. expect(screen.queryByLabelText(OptionsPaneSelector.fieldLabel('TestPanel HiddenFromDef'))).not.toBeInTheDocument();
  129. });
  130. it('should create categories for field options with category', () => {
  131. const scenario = new OptionsPaneOptionsTestScenario();
  132. scenario.render();
  133. expect(screen.getByRole('heading', { name: /Axis/ })).toBeInTheDocument();
  134. });
  135. it('should not render categories with hidden fields only', () => {
  136. const scenario = new OptionsPaneOptionsTestScenario();
  137. scenario.plugin = getPanelPlugin({
  138. id: 'TestPanel',
  139. }).useFieldConfig({
  140. standardOptions: {},
  141. useCustomConfig: (b) => {
  142. b.addBooleanSwitch({
  143. name: 'CustomBool',
  144. path: 'CustomBool',
  145. hideFromDefaults: true,
  146. category: ['Axis'],
  147. });
  148. },
  149. });
  150. scenario.render();
  151. expect(screen.queryByRole('heading', { name: /Axis/ })).not.toBeInTheDocument();
  152. });
  153. it('should call onPanelConfigChange when updating title', () => {
  154. const scenario = new OptionsPaneOptionsTestScenario();
  155. scenario.render();
  156. const input = screen.getByDisplayValue(scenario.panel.title);
  157. fireEvent.change(input, { target: { value: 'New' } });
  158. fireEvent.blur(input);
  159. expect(scenario.onPanelConfigChange).toHaveBeenCalledWith('title', 'New');
  160. });
  161. it('should call onFieldConfigsChange when updating field config', () => {
  162. const scenario = new OptionsPaneOptionsTestScenario();
  163. scenario.render();
  164. const input = screen.getByPlaceholderText('CustomTextPropPlaceholder');
  165. fireEvent.change(input, { target: { value: 'New' } });
  166. fireEvent.blur(input);
  167. const newFieldConfig: FieldConfigSource = scenario.panel.fieldConfig;
  168. newFieldConfig.defaults.custom = { TextPropWithCategory: 'New' };
  169. expect(scenario.onFieldConfigsChange).toHaveBeenCalledWith(newFieldConfig);
  170. });
  171. it('should only render hits when search query specified', async () => {
  172. const scenario = new OptionsPaneOptionsTestScenario();
  173. scenario.render();
  174. const input = screen.getByPlaceholderText('Search options');
  175. fireEvent.change(input, { target: { value: 'TextPropWithCategory' } });
  176. fireEvent.blur(input);
  177. expect(screen.queryByLabelText(OptionsPaneSelector.fieldLabel('Panel options Title'))).not.toBeInTheDocument();
  178. expect(screen.getByLabelText(OptionsPaneSelector.fieldLabel('Axis TextPropWithCategory'))).toBeInTheDocument();
  179. });
  180. it('should not render field override options non data panel', async () => {
  181. const scenario = new OptionsPaneOptionsTestScenario();
  182. scenario.plugin = getPanelPlugin({
  183. id: 'TestPanel',
  184. });
  185. scenario.render();
  186. expect(
  187. screen.queryByLabelText(selectors.components.ValuePicker.button('Add field override'))
  188. ).not.toBeInTheDocument();
  189. });
  190. it('should allow standard properties extension', async () => {
  191. const scenario = new OptionsPaneOptionsTestScenario();
  192. scenario.plugin = getPanelPlugin({
  193. id: 'TestPanel',
  194. }).useFieldConfig({
  195. useCustomConfig: (b) => {
  196. b.addBooleanSwitch({
  197. name: 'CustomThresholdOption',
  198. path: 'CustomThresholdOption',
  199. category: ['Thresholds'],
  200. });
  201. },
  202. });
  203. scenario.render();
  204. const thresholdsSection = screen.getByLabelText(selectors.components.OptionsGroup.group('Thresholds'));
  205. expect(
  206. within(thresholdsSection).getByLabelText(OptionsPaneSelector.fieldLabel('Thresholds CustomThresholdOption'))
  207. ).toBeInTheDocument();
  208. });
  209. it('should show data override info dot', async () => {
  210. const scenario = new OptionsPaneOptionsTestScenario();
  211. scenario.panelData.series = [
  212. toDataFrame({
  213. fields: [
  214. {
  215. name: 'Value',
  216. type: FieldType.number,
  217. values: [10, 200],
  218. config: {
  219. min: 100,
  220. },
  221. },
  222. ],
  223. refId: 'A',
  224. }),
  225. ];
  226. scenario.render();
  227. expect(screen.getByLabelText(dataOverrideTooltipDescription)).toBeInTheDocument();
  228. expect(screen.queryByLabelText(overrideRuleTooltipDescription)).not.toBeInTheDocument();
  229. });
  230. it('should show override rule info dot', async () => {
  231. const scenario = new OptionsPaneOptionsTestScenario();
  232. scenario.panel.fieldConfig.overrides = [
  233. {
  234. matcher: { id: 'byName', options: 'SeriesA' },
  235. properties: [
  236. {
  237. id: 'decimals',
  238. value: 2,
  239. },
  240. ],
  241. },
  242. ];
  243. scenario.render();
  244. expect(screen.getByLabelText(overrideRuleTooltipDescription)).toBeInTheDocument();
  245. });
  246. });