diff --git a/public/app/core/components/OptionsUI/NumberInput.test.tsx b/public/app/core/components/OptionsUI/NumberInput.test.tsx new file mode 100644 index 00000000000..2538f17aa14 --- /dev/null +++ b/public/app/core/components/OptionsUI/NumberInput.test.tsx @@ -0,0 +1,110 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import React from 'react'; + +import { NumberInput } from './NumberInput'; + +const setup = (min?: number, max?: number) => { + const onChange = jest.fn(); + render(); + return { + input: screen.getByTestId('input-wrapper').firstChild?.firstChild as HTMLInputElement, + onChange, + }; +}; + +describe('NumberInput', () => { + it('updated input correctly', () => { + const data = setup(); + + const tests = [ + { + value: '-10', + expected: -10, + onChangeCalledWith: -10, + }, + { + value: '', + expected: null, + onChangeCalledWith: undefined, + }, + { + value: '100', + expected: 100, + onChangeCalledWith: 100, + }, + { + value: '1asd', + expected: null, + onChangeCalledWith: undefined, + }, + { + value: -100, + expected: -100, + onChangeCalledWith: -100, + }, + { + value: 20, + expected: 20, + onChangeCalledWith: 20, + }, + { + value: 0, + expected: 0, + onChangeCalledWith: 0, + }, + { + value: '0', + expected: 0, + onChangeCalledWith: 0, + }, + ]; + + tests.forEach((test, i) => { + fireEvent.blur(data.input, { target: { value: test.value } }); + expect(data.onChange).toBeCalledWith(test.onChangeCalledWith); + expect(data.onChange).toBeCalledTimes(i + 1); + expect(data.input).toHaveValue(test.expected); + }); + }); + + it('corrects input as per min and max', async () => { + const data = setup(-10, 10); + let input = data.input; + + const tests = [ + { + value: '-10', + expected: -10, + onChangeCalledWith: -10, + }, + { + value: '-100', + expected: -10, + onChangeCalledWith: -10, + }, + { + value: '10', + expected: 10, + onChangeCalledWith: 10, + }, + { + value: '100', + expected: 10, + onChangeCalledWith: 10, + }, + { + value: '5', + expected: 5, + onChangeCalledWith: 5, + }, + ]; + + tests.forEach((test, i) => { + input = screen.getByTestId('input-wrapper').firstChild?.firstChild as HTMLInputElement; + fireEvent.blur(input, { target: { value: test.value } }); + expect(data.onChange).toBeCalledWith(test.onChangeCalledWith); + expect(data.onChange).toBeCalledTimes(i + 1); + expect(screen.getByTestId('input-wrapper').firstChild?.firstChild).toHaveValue(test.expected); + }); + }); +}); diff --git a/public/app/core/components/OptionsUI/NumberInput.tsx b/public/app/core/components/OptionsUI/NumberInput.tsx index 2cd0007b0c7..c507b421f48 100644 --- a/public/app/core/components/OptionsUI/NumberInput.tsx +++ b/public/app/core/components/OptionsUI/NumberInput.tsx @@ -50,33 +50,31 @@ export class NumberInput extends PureComponent { let newValue = ''; const min = this.props.min; const max = this.props.max; - const currentValue = txt && +txt; + let currentValue = txt !== '' ? Number(txt) : undefined; - if (currentValue) { - if (!Number.isNaN(currentValue)) { - if (min != null && currentValue < min) { - newValue = min.toString(); - corrected = true; - } else if (max != null && currentValue > max) { - newValue = max.toString(); - corrected = true; - } else { - newValue = txt; - } + if (currentValue && !Number.isNaN(currentValue)) { + if (min != null && currentValue < min) { + newValue = min.toString(); + corrected = true; + } else if (max != null && currentValue > max) { + newValue = max.toString(); + corrected = true; + } else { + newValue = txt ?? ''; } this.setState({ - text: newValue || '', + text: newValue, inputCorrected: corrected, }); + } - if (corrected) { - this.updateValueDebounced(); - } + if (corrected) { + this.updateValueDebounced(); + } - if (!isNaN(currentValue) && currentValue !== this.props.value) { - this.props.onChange(currentValue); - } + if (!Number.isNaN(currentValue) && currentValue !== this.props.value) { + this.props.onChange(currentValue); } };