import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { LokiQuery, LokiQueryType } from '../../types'; import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions'; describe('LokiQueryBuilderOptions', () => { it('can change query type', async () => { const { props } = setup(); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.getByLabelText('Range')).toBeChecked(); await userEvent.click(screen.getByLabelText('Instant')); expect(props.onChange).toHaveBeenCalledWith({ ...props.query, queryType: LokiQueryType.Instant, }); }); it('can change legend format', async () => { const { props } = setup(); await userEvent.click(screen.getByRole('button', { name: /Options/ })); // First autosize input is a Legend const element = screen.getAllByTestId('autosize-input')[0]; await userEvent.type(element, 'asd'); await userEvent.keyboard('{enter}'); expect(props.onChange).toHaveBeenCalledWith({ ...props.query, legendFormat: 'asd', }); }); it('can change line limit to valid value', async () => { const { props } = setup({ expr: '{foo="bar"}' }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit const element = screen.getAllByTestId('autosize-input')[1]; await userEvent.type(element, '10'); await userEvent.keyboard('{enter}'); expect(props.onChange).toHaveBeenCalledWith({ ...props.query, maxLines: 10, }); }); it('does not change line limit to invalid numeric value', async () => { const { props } = setup({ expr: '{foo="bar"}' }); // We need to start with some value to be able to change it props.query.maxLines = 10; await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit const element = screen.getAllByTestId('autosize-input')[1]; await userEvent.type(element, '-10'); await userEvent.keyboard('{enter}'); expect(props.onChange).toHaveBeenCalledWith({ ...props.query, maxLines: undefined, }); }); it('does not change line limit to invalid text value', async () => { const { props } = setup({ expr: '{foo="bar"}' }); // We need to start with some value to be able to change it props.query.maxLines = 10; await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit const element = screen.getAllByTestId('autosize-input')[1]; await userEvent.type(element, 'asd'); await userEvent.keyboard('{enter}'); expect(props.onChange).toHaveBeenCalledWith({ ...props.query, maxLines: undefined, }); }); it('shows correct options for log query', async () => { setup({ expr: '{foo="bar"}' }); expect(screen.getByText('Line limit: 20')).toBeInTheDocument(); expect(screen.getByText('Type: Range')).toBeInTheDocument(); expect(screen.queryByText(/step/i)).not.toBeInTheDocument(); }); it('shows correct options for metric query', async () => { setup({ expr: 'rate({foo="bar"}[5m]', step: '1m', resolution: 2 }); expect(screen.queryByText('Line limit: 20')).not.toBeInTheDocument(); expect(screen.getByText('Type: Range')).toBeInTheDocument(); expect(screen.getByText('Step: 1m')).toBeInTheDocument(); expect(screen.getByText('Resolution: 1/2')).toBeInTheDocument(); }); it('does not show resolution field if resolution is not set', async () => { setup({ expr: 'rate({foo="bar"}[5m]' }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.queryByText('Resolution')).not.toBeInTheDocument(); }); it('does not show resolution field if resolution is set to default value 1', async () => { setup({ expr: 'rate({foo="bar"}[5m]', resolution: 1 }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.queryByText('Resolution')).not.toBeInTheDocument(); }); it('does shows resolution field with warning if resolution is set to non-default value', async () => { setup({ expr: 'rate({foo="bar"}[5m]', resolution: 2 }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.getByText('Resolution')).toBeInTheDocument(); expect( screen.getByText("The 'Resolution' is deprecated. Use 'Step' editor instead to change step parameter.") ).toBeInTheDocument(); }); it.each(['abc', 10])('shows correct options for metric query with invalid step', async (step: string | number) => { // @ts-expect-error Expected for backward compatibility test setup({ expr: 'rate({foo="bar"}[5m]', step }); expect(screen.queryByText('Line limit: 20')).not.toBeInTheDocument(); expect(screen.getByText('Type: Range')).toBeInTheDocument(); expect(screen.getByText('Step: Invalid value')).toBeInTheDocument(); }); it('shows error when invalid value in step', async () => { setup({ expr: 'rate({foo="bar"}[5m]', step: 'a' }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.getByText(/Invalid step/)).toBeInTheDocument(); }); it('does not show error when valid value in step', async () => { setup({ expr: 'rate({foo="bar"}[5m]', step: '1m' }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument(); }); it('does not show error when valid millisecond value in step', async () => { setup({ expr: 'rate({foo="bar"}[5m]', step: '1ms' }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument(); }); it('does not show error when valid day value in step', async () => { setup({ expr: 'rate({foo="bar"}[5m]', step: '1d' }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument(); }); it('does not show instant type when using a log query', async () => { setup({ expr: '{foo="bar"}', queryType: LokiQueryType.Instant }); expect(screen.queryByText(/Instant/)).not.toBeInTheDocument(); }); it('does not show instant type in the options when using a log query', async () => { setup({ expr: '{foo="bar"}', step: '1m' }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.queryByText(/Instant/)).not.toBeInTheDocument(); }); it('allows to clear step input', async () => { setup({ expr: 'rate({foo="bar"}[5m]', step: '4s' }); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(screen.getByDisplayValue('4s')).toBeInTheDocument(); await userEvent.clear(screen.getByDisplayValue('4s')); expect(screen.queryByDisplayValue('4s')).not.toBeInTheDocument(); }); it('should transform non duration numbers to duration', async () => { const onChange = jest.fn(); setup({ expr: 'rate({foo="bar"}[5m]', step: '4' }, onChange); await userEvent.click(screen.getByRole('button', { name: /Options/ })); expect(onChange).toHaveBeenCalledWith({ refId: 'A', expr: 'rate({foo="bar"}[5m]', step: '4s', }); }); }); function setup(queryOverrides: Partial = {}, onChange = jest.fn()) { const props = { query: { refId: 'A', expr: '', ...queryOverrides, }, onRunQuery: jest.fn(), onChange, maxLines: 20, queryStats: { streams: 0, chunks: 0, bytes: 0, entries: 0 }, }; const { container } = render(); return { container, props }; }