import { fireEvent, render, screen, within } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; import configureMockStore from 'redux-mock-store'; import { FieldConfigSource, FieldType, LoadingState, PanelData, standardEditorsRegistry, standardFieldConfigEditorRegistry, toDataFrame, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/editors/registry'; import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; import { DashboardModel, PanelModel } from '../../state'; import { OptionsPaneOptions } from './OptionsPaneOptions'; import { dataOverrideTooltipDescription, overrideRuleTooltipDescription } from './state/getOptionOverrides'; standardEditorsRegistry.setInit(getAllOptionEditors); standardFieldConfigEditorRegistry.setInit(getAllStandardFieldConfigs); const mockStore = configureMockStore(); const OptionsPaneSelector = selectors.components.PanelEditor.OptionsPane; jest.mock('react-router-dom', () => ({ ...(jest.requireActual('react-router-dom') as any), useLocation: () => ({ pathname: 'localhost:3000/example/path', }), })); class OptionsPaneOptionsTestScenario { onFieldConfigsChange = jest.fn(); onPanelOptionsChanged = jest.fn(); onPanelConfigChange = jest.fn(); panelData: PanelData = { series: [], state: LoadingState.Done, timeRange: {} as any, }; plugin = getPanelPlugin({ id: 'TestPanel', }).useFieldConfig({ standardOptions: {}, useCustomConfig: (b) => { b.addBooleanSwitch({ name: 'CustomBool', path: 'CustomBool', }) .addBooleanSwitch({ name: 'HiddenFromDef', path: 'HiddenFromDef', hideFromDefaults: true, }) .addTextInput({ name: 'TextPropWithCategory', path: 'TextPropWithCategory', settings: { placeholder: 'CustomTextPropPlaceholder', }, category: ['Axis'], }); }, }); panel = new PanelModel({ title: 'Test title', type: this.plugin.meta.id, fieldConfig: { defaults: { max: 100, thresholds: { mode: 'absolute', steps: [ { value: -Infinity, color: 'green' }, { value: 100, color: 'green' }, ], }, }, overrides: [], }, options: {}, }); dashboard = new DashboardModel({}); store = mockStore({ dashboard: { panels: [] }, templating: { variables: {}, }, }); render() { render( ); } } describe('OptionsPaneOptions', () => { it('should render panel frame options', async () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.render(); expect(screen.getByLabelText(OptionsPaneSelector.fieldLabel('Panel options Title'))).toBeInTheDocument(); }); it('should render all categories', async () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.render(); expect(screen.getByRole('heading', { name: /Panel options/ })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /Standard options/ })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /Value mappings/ })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /Thresholds/ })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /TestPanel/ })).toBeInTheDocument(); }); it('should render custom options', () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.render(); expect(screen.getByLabelText(OptionsPaneSelector.fieldLabel('TestPanel CustomBool'))).toBeInTheDocument(); }); it('should not render options that are marked as hidden from defaults', () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.render(); expect(screen.queryByLabelText(OptionsPaneSelector.fieldLabel('TestPanel HiddenFromDef'))).not.toBeInTheDocument(); }); it('should create categories for field options with category', () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.render(); expect(screen.getByRole('heading', { name: /Axis/ })).toBeInTheDocument(); }); it('should not render categories with hidden fields only', () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.plugin = getPanelPlugin({ id: 'TestPanel', }).useFieldConfig({ standardOptions: {}, useCustomConfig: (b) => { b.addBooleanSwitch({ name: 'CustomBool', path: 'CustomBool', hideFromDefaults: true, category: ['Axis'], }); }, }); scenario.render(); expect(screen.queryByRole('heading', { name: /Axis/ })).not.toBeInTheDocument(); }); it('should call onPanelConfigChange when updating title', () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.render(); const input = screen.getByDisplayValue(scenario.panel.title); fireEvent.change(input, { target: { value: 'New' } }); fireEvent.blur(input); expect(scenario.onPanelConfigChange).toHaveBeenCalledWith('title', 'New'); }); it('should call onFieldConfigsChange when updating field config', () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.render(); const input = screen.getByPlaceholderText('CustomTextPropPlaceholder'); fireEvent.change(input, { target: { value: 'New' } }); fireEvent.blur(input); const newFieldConfig: FieldConfigSource = scenario.panel.fieldConfig; newFieldConfig.defaults.custom = { TextPropWithCategory: 'New' }; expect(scenario.onFieldConfigsChange).toHaveBeenCalledWith(newFieldConfig); }); it('should only render hits when search query specified', async () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.render(); const input = screen.getByPlaceholderText('Search options'); fireEvent.change(input, { target: { value: 'TextPropWithCategory' } }); fireEvent.blur(input); expect(screen.queryByLabelText(OptionsPaneSelector.fieldLabel('Panel options Title'))).not.toBeInTheDocument(); expect(screen.getByLabelText(OptionsPaneSelector.fieldLabel('Axis TextPropWithCategory'))).toBeInTheDocument(); }); it('should not render field override options non data panel', async () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.plugin = getPanelPlugin({ id: 'TestPanel', }); scenario.render(); expect( screen.queryByLabelText(selectors.components.ValuePicker.button('Add field override')) ).not.toBeInTheDocument(); }); it('should allow standard properties extension', async () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.plugin = getPanelPlugin({ id: 'TestPanel', }).useFieldConfig({ useCustomConfig: (b) => { b.addBooleanSwitch({ name: 'CustomThresholdOption', path: 'CustomThresholdOption', category: ['Thresholds'], }); }, }); scenario.render(); const thresholdsSection = screen.getByLabelText(selectors.components.OptionsGroup.group('Thresholds')); expect( within(thresholdsSection).getByLabelText(OptionsPaneSelector.fieldLabel('Thresholds CustomThresholdOption')) ).toBeInTheDocument(); }); it('should show data override info dot', async () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.panelData.series = [ toDataFrame({ fields: [ { name: 'Value', type: FieldType.number, values: [10, 200], config: { min: 100, }, }, ], refId: 'A', }), ]; scenario.render(); expect(screen.getByLabelText(dataOverrideTooltipDescription)).toBeInTheDocument(); expect(screen.queryByLabelText(overrideRuleTooltipDescription)).not.toBeInTheDocument(); }); it('should show override rule info dot', async () => { const scenario = new OptionsPaneOptionsTestScenario(); scenario.panel.fieldConfig.overrides = [ { matcher: { id: 'byName', options: 'SeriesA' }, properties: [ { id: 'decimals', value: 2, }, ], }, ]; scenario.render(); expect(screen.getByLabelText(overrideRuleTooltipDescription)).toBeInTheDocument(); }); });