[Vis: Default editor] Unit tests for number list (#48412)

* Add unit tests for number list

* Remove unused dependency

* Fix code review comments

* Refactor NumberList to set model validity in one place
This commit is contained in:
Maryia Lapata 2019-10-28 15:50:39 +03:00 committed by GitHub
parent 17464f8612
commit fb903f4f9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 652 additions and 105 deletions

View file

@ -0,0 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NumberList should be rendered with default set of props 1`] = `
<Fragment>
<NumberRow
autoFocus={false}
disableDelete={false}
isInvalid={false}
labelledbyId="numberList"
model={
Object {
"id": "122",
"isInvalid": false,
"value": 1,
}
}
onBlur={[MockFunction]}
onChange={[Function]}
onDelete={[Function]}
range={
Range {
"max": 10,
"maxInclusive": true,
"min": 1,
"minInclusive": true,
}
}
/>
<EuiSpacer
size="s"
/>
<NumberRow
autoFocus={true}
disableDelete={false}
isInvalid={false}
labelledbyId="numberList"
model={
Object {
"id": "123",
"isInvalid": false,
"value": 2,
}
}
onBlur={[MockFunction]}
onChange={[Function]}
onDelete={[Function]}
range={
Range {
"max": 10,
"maxInclusive": true,
"min": 1,
"minInclusive": true,
}
}
/>
<EuiSpacer
size="s"
/>
<EuiFlexItem>
<EuiButtonEmpty
iconType="plusInCircleFilled"
onClick={[Function]}
size="xs"
>
<FormattedMessage
defaultMessage="Add {unitName}"
id="common.ui.aggTypes.numberList.addUnitButtonLabel"
values={
Object {
"unitName": "value",
}
}
/>
</EuiButtonEmpty>
</EuiFlexItem>
</Fragment>
`;

View file

@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NumberRow should be rendered with default set of props 1`] = `
<EuiFlexGroup
alignItems="center"
gutterSize="s"
responsive={false}
>
<EuiFlexItem>
<EuiFieldNumber
aria-labelledby="numberList"
autoFocus={false}
compressed={true}
fullWidth={true}
isInvalid={false}
isLoading={false}
max={10}
min={1}
onBlur={[MockFunction]}
onChange={[Function]}
placeholder="Enter a value"
value={1}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiButtonIcon
aria-label="Remove the rank value of 1"
color="danger"
disabled={false}
iconType="trash"
onClick={[Function]}
title="Remove the rank value of 1"
/>
</EuiFlexItem>
</EuiFlexGroup>
`;

View file

@ -0,0 +1,147 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { NumberList, NumberListProps } from './number_list';
import { NumberRow } from './number_row';
jest.mock('./number_row', () => ({
NumberRow: () => 'NumberRow',
}));
jest.mock('@elastic/eui', () => ({
htmlIdGenerator: jest.fn(() => {
let counter = 1;
return () => `12${counter++}`;
}),
EuiSpacer: require.requireActual('@elastic/eui').EuiSpacer,
EuiFlexItem: require.requireActual('@elastic/eui').EuiFlexItem,
EuiButtonEmpty: require.requireActual('@elastic/eui').EuiButtonEmpty,
EuiFormErrorText: require.requireActual('@elastic/eui').EuiFormErrorText,
}));
describe('NumberList', () => {
let defaultProps: NumberListProps;
beforeEach(() => {
defaultProps = {
labelledbyId: 'numberList',
numberArray: [1, 2],
range: '[1, 10]',
showValidation: false,
unitName: 'value',
onChange: jest.fn(),
setTouched: jest.fn(),
setValidity: jest.fn(),
};
});
test('should be rendered with default set of props', () => {
const comp = shallow(<NumberList {...defaultProps} />);
expect(comp).toMatchSnapshot();
});
test('should show an order error', () => {
defaultProps.numberArray = [3, 1];
defaultProps.showValidation = true;
const comp = mountWithIntl(<NumberList {...defaultProps} />);
expect(comp.find('EuiFormErrorText').length).toBe(1);
});
test('should set validity as true', () => {
mountWithIntl(<NumberList {...defaultProps} />);
expect(defaultProps.setValidity).lastCalledWith(true);
});
test('should set validity as false when the order is invalid', () => {
defaultProps.numberArray = [3, 2];
const comp = mountWithIntl(<NumberList {...defaultProps} />);
expect(defaultProps.setValidity).lastCalledWith(false);
comp.setProps({ numberArray: [1, 2] });
expect(defaultProps.setValidity).lastCalledWith(true);
});
test('should set validity as false when there is an empty field', () => {
defaultProps.numberArray = [1, 2];
const comp = mountWithIntl(<NumberList {...defaultProps} />);
comp.setProps({ numberArray: [1, undefined] });
expect(defaultProps.setValidity).lastCalledWith(false);
});
test('should set 0 when number array is empty', () => {
defaultProps.numberArray = [];
mountWithIntl(<NumberList {...defaultProps} />);
expect(defaultProps.onChange).lastCalledWith([0]);
});
test('should add a number', () => {
const comp = shallow(<NumberList {...defaultProps} />);
comp.find('EuiButtonEmpty').simulate('click');
expect(defaultProps.onChange).lastCalledWith([1, 2, 3]);
});
test('should remove a number', () => {
const comp = shallow(<NumberList {...defaultProps} />);
const row = comp.find(NumberRow).first();
row.prop('onDelete')(row.prop('model').id);
expect(defaultProps.onChange).lastCalledWith([2]);
});
test('should disable remove button if there is one number', () => {
defaultProps.numberArray = [1];
const comp = shallow(<NumberList {...defaultProps} />);
expect(
comp
.find(NumberRow)
.first()
.prop('disableDelete')
).toEqual(true);
});
test('should change value', () => {
const comp = shallow(<NumberList {...defaultProps} />);
const row = comp.find(NumberRow).first();
row.prop('onChange')({ id: row.prop('model').id, value: '3' });
expect(defaultProps.onChange).lastCalledWith([3, 2]);
});
test('should call setTouched', () => {
const comp = shallow(<NumberList {...defaultProps} />);
comp
.find(NumberRow)
.first()
.prop('onBlur')();
expect(defaultProps.setTouched).toBeCalled();
});
});

View file

@ -17,7 +17,7 @@
* under the License.
*/
import React, { Fragment, useState, useEffect } from 'react';
import React, { Fragment, useState, useEffect, useMemo, useCallback } from 'react';
import { EuiSpacer, EuiButtonEmpty, EuiFlexItem, EuiFormErrorText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@ -34,16 +34,15 @@ import {
getUpdatedModels,
hasInvalidValues,
} from './utils';
import { useValidation } from '../../agg_utils';
interface NumberListProps {
export interface NumberListProps {
labelledbyId: string;
numberArray: Array<number | undefined>;
range?: string;
showValidation: boolean;
unitName: string;
validateAscendingOrder?: boolean;
onBlur?(): void;
onFocus?(): void;
onChange(list: Array<number | undefined>): void;
setTouched(): void;
setValidity(isValid: boolean): void;
@ -56,81 +55,84 @@ function NumberList({
showValidation,
unitName,
validateAscendingOrder = true,
onBlur,
onFocus,
onChange,
setTouched,
setValidity,
}: NumberListProps) {
const numberRange = getRange(range);
const numberRange = useMemo(() => getRange(range), [range]);
const [models, setModels] = useState(getInitModelList(numberArray));
const [ascendingError, setAscendingError] = useState(EMPTY_STRING);
// responsible for discarding changes
// set up validity for each model
useEffect(() => {
const updatedModels = getUpdatedModels(numberArray, models, numberRange);
let id: number | undefined;
if (validateAscendingOrder) {
const isOrderValid = validateOrder(updatedModels);
const { isValidOrder, modelIndex } = validateOrder(numberArray);
id = isValidOrder ? undefined : modelIndex;
setAscendingError(
isOrderValid
? i18n.translate('common.ui.aggTypes.numberList.invalidAscOrderErrorMessage', {
isValidOrder
? EMPTY_STRING
: i18n.translate('common.ui.aggTypes.numberList.invalidAscOrderErrorMessage', {
defaultMessage: 'The values should be in ascending order.',
})
: EMPTY_STRING
);
}
setModels(updatedModels);
}, [numberArray]);
setModels(state => getUpdatedModels(numberArray, state, numberRange, id));
}, [numberArray, numberRange, validateAscendingOrder]);
// responsible for setting up an initial value ([0]) when there is no default value
useEffect(() => {
setValidity(!hasInvalidValues(models));
}, [models]);
// resposible for setting up an initial value ([0]) when there is no default value
useEffect(() => {
onChange(models.map(({ value }) => (value === EMPTY_STRING ? undefined : value)));
if (!numberArray.length) {
onChange([models[0].value as number]);
}
}, []);
const onChangeValue = ({ id, value }: { id: string; value: string }) => {
const parsedValue = parse(value);
const { isValid, errors } = validateValue(parsedValue, numberRange);
setValidity(isValid);
const isValid = !hasInvalidValues(models);
useValidation(setValidity, isValid);
const currentModel = models.find(model => model.id === id);
if (currentModel) {
currentModel.value = parsedValue;
currentModel.isInvalid = !isValid;
currentModel.errors = errors;
}
const onUpdate = useCallback(
(modelList: NumberRowModel[]) => {
setModels(modelList);
onChange(modelList.map(({ value }) => (value === EMPTY_STRING ? undefined : value)));
},
[onChange]
);
onUpdate(models);
};
const onChangeValue = useCallback(
({ id, value }: { id: string; value: string }) => {
const parsedValue = parse(value);
onUpdate(
models.map(model => {
if (model.id === id) {
const { isInvalid, error } = validateValue(parsedValue, numberRange);
return {
id,
value: parsedValue,
isInvalid,
error,
};
}
return model;
})
);
},
[numberRange, models, onUpdate]
);
// Add an item to the end of the list
const onAdd = () => {
const onAdd = useCallback(() => {
const newArray = [...models, getNextModel(models, numberRange)];
onUpdate(newArray);
};
}, [models, numberRange, onUpdate]);
const onDelete = (id: string) => {
const newArray = models.filter(model => model.id !== id);
onUpdate(newArray);
};
const onBlurFn = (model: NumberRowModel) => {
if (model.value === EMPTY_STRING) {
model.isInvalid = true;
}
setTouched();
if (onBlur) {
onBlur();
}
};
const onUpdate = (modelList: NumberRowModel[]) => {
setModels(modelList);
onChange(modelList.map(({ value }) => (value === EMPTY_STRING ? undefined : value)));
};
const onDelete = useCallback(
(id: string) => {
const newArray = models.filter(model => model.id !== id);
onUpdate(newArray);
},
[models, onUpdate]
);
return (
<>
@ -143,13 +145,12 @@ function NumberList({
labelledbyId={labelledbyId}
range={numberRange}
onDelete={onDelete}
onFocus={onFocus}
onChange={onChangeValue}
onBlur={() => onBlurFn(model)}
onBlur={setTouched}
autoFocus={models.length !== 1 && arrayIndex === models.length - 1}
/>
{showValidation && model.isInvalid && model.errors && model.errors.length > 0 && (
<EuiFormErrorText>{model.errors.join('\n')}</EuiFormErrorText>
{showValidation && model.isInvalid && model.error && (
<EuiFormErrorText>{model.error}</EuiFormErrorText>
)}
{models.length - 1 !== arrayIndex && <EuiSpacer size="s" />}
</Fragment>

View file

@ -0,0 +1,69 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { NumberRow, NumberRowProps } from './number_row';
describe('NumberRow', () => {
let defaultProps: NumberRowProps;
beforeEach(() => {
defaultProps = {
autoFocus: false,
disableDelete: false,
isInvalid: false,
labelledbyId: 'numberList',
model: { value: 1, id: '1', isInvalid: false },
range: {
min: 1,
max: 10,
minInclusive: true,
maxInclusive: true,
within: jest.fn(() => true),
},
onChange: jest.fn(),
onBlur: jest.fn(),
onDelete: jest.fn(),
};
});
test('should be rendered with default set of props', () => {
const comp = shallow(<NumberRow {...defaultProps} />);
expect(comp).toMatchSnapshot();
});
test('should call onDelete', () => {
const comp = shallow(<NumberRow {...defaultProps} />);
comp.find('EuiButtonIcon').simulate('click');
expect(defaultProps.onDelete).lastCalledWith(defaultProps.model.id);
});
test('should call onChange', () => {
const comp = shallow(<NumberRow {...defaultProps} />);
comp.find('EuiFieldNumber').prop('onChange')!({ target: { value: '5' } } as React.ChangeEvent<
HTMLInputElement
>);
expect(defaultProps.onChange).lastCalledWith({ id: defaultProps.model.id, value: '5' });
});
});

View file

@ -17,13 +17,13 @@
* under the License.
*/
import React from 'react';
import React, { useCallback } from 'react';
import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Range } from '../../../../../../utils/range';
interface NumberRowProps {
export interface NumberRowProps {
autoFocus: boolean;
disableDelete: boolean;
isInvalid: boolean;
@ -31,7 +31,6 @@ interface NumberRowProps {
model: NumberRowModel;
range: Range;
onBlur(): void;
onFocus?(): void;
onChange({ id, value }: { id: string; value: string }): void;
onDelete(index: string): void;
}
@ -40,7 +39,7 @@ export interface NumberRowModel {
id: string;
isInvalid: boolean;
value: number | '';
errors?: string[];
error?: string;
}
function NumberRow({
@ -52,7 +51,6 @@ function NumberRow({
range,
onBlur,
onDelete,
onFocus,
onChange,
}: NumberRowProps) {
const deleteBtnAriaLabel = i18n.translate(
@ -63,11 +61,16 @@ function NumberRow({
}
);
const onValueChanged = (event: React.ChangeEvent<HTMLInputElement>) =>
onChange({
value: event.target.value,
id: model.id,
});
const onValueChanged = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) =>
onChange({
value: event.target.value,
id: model.id,
}),
[onChange, model.id]
);
const onDeleteFn = useCallback(() => onDelete(model.id), [onDelete, model.id]);
return (
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="s">
@ -81,7 +84,6 @@ function NumberRow({
defaultMessage: 'Enter a value',
})}
onChange={onValueChanged}
onFocus={onFocus}
value={model.value}
fullWidth={true}
min={range.min}
@ -95,7 +97,7 @@ function NumberRow({
title={deleteBtnAriaLabel}
color="danger"
iconType="trash"
onClick={() => onDelete(model.id)}
onClick={onDeleteFn}
disabled={disableDelete}
/>
</EuiFlexItem>

View file

@ -0,0 +1,218 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
getInitModelList,
getUpdatedModels,
validateOrder,
hasInvalidValues,
parse,
validateValue,
getNextModel,
getRange,
} from './utils';
import { Range } from '../../../../../../utils/range';
import { NumberRowModel } from './number_row';
describe('NumberList utils', () => {
let modelList: NumberRowModel[];
let range: Range;
beforeEach(() => {
modelList = [{ value: 1, id: '1', isInvalid: false }, { value: 2, id: '2', isInvalid: false }];
range = {
min: 1,
max: 10,
minInclusive: true,
maxInclusive: true,
within: jest.fn(() => true),
};
});
describe('getInitModelList', () => {
test('should return list with default model when number list is empty', () => {
const models = getInitModelList([]);
expect(models).toEqual([{ value: 0, id: expect.any(String), isInvalid: false }]);
});
test('should return model list', () => {
const models = getInitModelList([1, undefined]);
expect(models).toEqual([
{ value: 1, id: expect.any(String), isInvalid: false },
{ value: '', id: expect.any(String), isInvalid: false },
]);
});
});
describe('getUpdatedModels', () => {
test('should return model list when number list is empty', () => {
const updatedModelList = getUpdatedModels([], modelList, range);
expect(updatedModelList).toEqual([{ value: 0, id: expect.any(String), isInvalid: false }]);
});
test('should not update model list when number list is the same', () => {
const updatedModelList = getUpdatedModels([1, 2], modelList, range);
expect(updatedModelList).toEqual(modelList);
});
test('should update model list when number list was changed', () => {
const updatedModelList = getUpdatedModels([1, 3], modelList, range);
modelList[1].value = 3;
expect(updatedModelList).toEqual(modelList);
});
test('should update model list when number list increased', () => {
const updatedModelList = getUpdatedModels([1, 2, 3], modelList, range);
expect(updatedModelList).toEqual([
...modelList,
{ value: 3, id: expect.any(String), isInvalid: false },
]);
});
test('should update model list when number list decreased', () => {
const updatedModelList = getUpdatedModels([2], modelList, range);
expect(updatedModelList).toEqual([{ value: 2, id: '1', isInvalid: false }]);
});
test('should update model list when number list has undefined value', () => {
const updatedModelList = getUpdatedModels([1, undefined], modelList, range);
modelList[1].value = '';
modelList[1].isInvalid = true;
expect(updatedModelList).toEqual(modelList);
});
test('should update model list when number order is invalid', () => {
const updatedModelList = getUpdatedModels([1, 3, 2], modelList, range, 2);
expect(updatedModelList).toEqual([
modelList[0],
{ ...modelList[1], value: 3 },
{ value: 2, id: expect.any(String), isInvalid: true },
]);
});
});
describe('validateOrder', () => {
test('should return true when order is valid', () => {
expect(validateOrder([1, 2])).toEqual({
isValidOrder: true,
});
});
test('should return true when a number is undefined', () => {
expect(validateOrder([1, undefined])).toEqual({
isValidOrder: true,
});
});
test('should return false when order is invalid', () => {
expect(validateOrder([2, 1])).toEqual({
isValidOrder: false,
modelIndex: 1,
});
});
});
describe('hasInvalidValues', () => {
test('should return false when there are no invalid models', () => {
expect(hasInvalidValues(modelList)).toBeFalsy();
});
test('should return true when there is an invalid model', () => {
modelList[1].isInvalid = true;
expect(hasInvalidValues(modelList)).toBeTruthy();
});
});
describe('parse', () => {
test('should return a number', () => {
expect(parse('3')).toBe(3);
});
test('should return an empty string when value is invalid', () => {
expect(parse('')).toBe('');
expect(parse('test')).toBe('');
expect(parse('NaN')).toBe('');
});
});
describe('validateValue', () => {
test('should return valid', () => {
expect(validateValue(3, range)).toEqual({ isInvalid: false });
});
test('should return invalid', () => {
range.within = jest.fn(() => false);
expect(validateValue(11, range)).toEqual({ isInvalid: true, error: expect.any(String) });
});
});
describe('getNextModel', () => {
test('should return 3 as next value', () => {
expect(getNextModel(modelList, range)).toEqual({
value: 3,
id: expect.any(String),
isInvalid: false,
});
});
test('should return 1 as next value', () => {
expect(getNextModel([{ value: '', id: '2', isInvalid: false }], range)).toEqual({
value: 1,
id: expect.any(String),
isInvalid: false,
});
});
test('should return 9 as next value', () => {
expect(getNextModel([{ value: 11, id: '2', isInvalid: false }], range)).toEqual({
value: 9,
id: expect.any(String),
isInvalid: false,
});
});
});
describe('getRange', () => {
test('should return default range', () => {
expect(getRange()).toEqual({
min: 0,
max: Infinity,
maxInclusive: false,
minInclusive: true,
});
});
test('should return parsed range', () => {
expect(getRange('(-Infinity, 100]')).toEqual({
min: -Infinity,
max: 100,
maxInclusive: true,
minInclusive: false,
});
});
test('should throw an error', () => {
expect(() => getRange('test')).toThrowError();
});
});
});

View file

@ -27,6 +27,7 @@ import { NumberRowModel } from './number_row';
const EMPTY_STRING = '';
const defaultRange = parseRange('[0,Infinity)');
const generateId = htmlIdGenerator();
const defaultModel = { value: 0, id: generateId(), isInvalid: false };
function parse(value: string) {
const parsedValue = parseFloat(value);
@ -42,44 +43,37 @@ function getRange(range?: string): Range {
}
function validateValue(value: number | '', numberRange: Range) {
const result = {
isValid: true,
errors: [] as string[],
const result: { isInvalid: boolean; error?: string } = {
isInvalid: false,
};
if (value === EMPTY_STRING) {
result.isValid = false;
result.isInvalid = true;
} else if (!numberRange.within(value)) {
result.isValid = false;
result.errors.push(
i18n.translate('common.ui.aggTypes.numberList.invalidRangeErrorMessage', {
defaultMessage: 'The value should be in the range of {min} to {max}.',
values: { min: numberRange.min, max: numberRange.max },
})
);
result.isInvalid = true;
result.error = i18n.translate('common.ui.aggTypes.numberList.invalidRangeErrorMessage', {
defaultMessage: 'The value should be in the range of {min} to {max}.',
values: { min: numberRange.min, max: numberRange.max },
});
}
return result;
}
function validateOrder(list: NumberRowModel[]) {
let isInvalidOrder = false;
list.forEach((model, index, array) => {
function validateOrder(list: Array<number | undefined>) {
const result: { isValidOrder: boolean; modelIndex?: number } = {
isValidOrder: true,
};
list.forEach((inputValue, index, array) => {
const previousModel = array[index - 1];
if (previousModel && model.value !== EMPTY_STRING) {
const isInvalidOrderOfItem = model.value <= previousModel.value;
if (!model.isInvalid && isInvalidOrderOfItem) {
model.isInvalid = true;
}
if (isInvalidOrderOfItem) {
isInvalidOrder = true;
}
if (previousModel !== undefined && inputValue !== undefined && inputValue <= previousModel) {
result.isValidOrder = false;
result.modelIndex = index;
}
});
return isInvalidOrder;
return result;
}
function getNextModel(list: NumberRowModel[], range: Range): NumberRowModel {
@ -104,26 +98,27 @@ function getInitModelList(list: Array<number | undefined>): NumberRowModel[] {
id: generateId(),
isInvalid: false,
}))
: [{ value: 0, id: generateId(), isInvalid: false }];
: [defaultModel];
}
function getUpdatedModels(
numberList: Array<number | undefined>,
modelList: NumberRowModel[],
numberRange: Range
numberRange: Range,
invalidOrderModelIndex?: number
): NumberRowModel[] {
if (!numberList.length) {
return modelList;
return [defaultModel];
}
return numberList.map((number, index) => {
const model = modelList[index] || { id: generateId() };
const newValue: NumberRowModel['value'] = number === undefined ? EMPTY_STRING : number;
const { isValid, errors } = validateValue(newValue, numberRange);
const { isInvalid, error } = validateValue(newValue, numberRange);
return {
...model,
value: newValue,
isInvalid: !isValid,
errors,
isInvalid: invalidOrderModelIndex === index ? true : isInvalid,
error,
};
});
}

View file

@ -37,7 +37,7 @@ function PercentileRanksEditor({
});
const [isValid, setIsValid] = useState(true);
const setModelValidy = (isListValid: boolean) => {
const setModelValidity = (isListValid: boolean) => {
setIsValid(isListValid);
setValidity(isListValid);
};
@ -62,7 +62,7 @@ function PercentileRanksEditor({
showValidation={showValidation}
onChange={setValue}
setTouched={setTouched}
setValidity={setModelValidy}
setValidity={setModelValidity}
/>
</EuiFormRow>
);

View file

@ -37,7 +37,7 @@ function PercentilesEditor({
});
const [isValid, setIsValid] = useState(true);
const setModelValidy = (isListValid: boolean) => {
const setModelValidity = (isListValid: boolean) => {
setIsValid(isListValid);
setValidity(isListValid);
};
@ -62,7 +62,7 @@ function PercentilesEditor({
showValidation={showValidation}
onChange={setValue}
setTouched={setTouched}
setValidity={setModelValidy}
setValidity={setModelValidity}
/>
</EuiFormRow>
);