diff --git a/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx b/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx index 52d9ea035..30958e470 100644 --- a/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx +++ b/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { render, screen } from '@testing-library/react'; +import { render, screen, within } from '@testing-library/react'; import MultiInputComponent, { MultiInputComponentProps } from './MultiInputComponent'; import { server } from '../../mocks/server'; @@ -160,3 +160,80 @@ describe.each(mockedEntries)('handler endpoint data loading', (mockedEntry) => { ); }); }); + +it('should render label (boolean-like)', () => { + renderFeature({ + value: 'true', + controlOptions: { + items: [ + { + label: 'truevalue', + value: true, + }, + { + label: 'falsevalue', + value: false, + }, + { + label: 'optionone', + value: 1, + }, + ], + }, + }); + const inputComponent = screen.getByTestId('multiselect'); + + expect(within(inputComponent).getByText('truevalue')).toBeInTheDocument(); + expect(within(inputComponent).queryByText('falsevalue')).not.toBeInTheDocument(); + expect(within(inputComponent).queryByText('optionone')).not.toBeInTheDocument(); +}); + +it('should render singe value (numeric)', () => { + renderFeature({ + value: 1, + controlOptions: { + items: [ + { + label: 'label1', + value: 1, + }, + { + label: 'label2', + value: 2, + }, + ], + }, + }); + const inputComponent = screen.getByTestId('multiselect'); + + expect(within(inputComponent).getByText('label1')).toBeInTheDocument(); + expect(within(inputComponent).queryByText('label2')).not.toBeInTheDocument(); +}); + +it('should render two values (number + boolean)', () => { + renderFeature({ + value: '1;false', + controlOptions: { + delimiter: ';', + items: [ + { + label: 'label1', + value: 1, + }, + { + label: 'label2', + value: false, + }, + { + label: 'label3', + value: 3, + }, + ], + }, + }); + const inputComponent = screen.getByTestId('multiselect'); + + expect(within(inputComponent).getByText('label1')).toBeInTheDocument(); + expect(within(inputComponent).getByText('label2')).toBeInTheDocument(); + expect(within(inputComponent).queryByText('label3')).not.toBeInTheDocument(); +}); diff --git a/ui/src/components/MultiInputComponent/MultiInputComponent.tsx b/ui/src/components/MultiInputComponent/MultiInputComponent.tsx index 28e2eb446..b0e7adbf8 100644 --- a/ui/src/components/MultiInputComponent/MultiInputComponent.tsx +++ b/ui/src/components/MultiInputComponent/MultiInputComponent.tsx @@ -8,6 +8,7 @@ import { AxiosCallType, axiosCallWrapper, generateEndPointUrl } from '../../util import { filterResponse } from '../../util/util'; import { MultipleSelectCommonOptions } from '../../types/globalConfig/entities'; import { invariant } from '../../util/invariant'; +import { AcceptableFormValue } from '../../types/components/shareableTypes'; const MultiSelectWrapper = styled(Multiselect)` width: 320px !important; @@ -23,7 +24,7 @@ export interface MultiInputComponentProps { field: string; controlOptions: z.TypeOf; disabled?: boolean; - value?: string; + value?: AcceptableFormValue; error?: boolean; dependencyValues?: Record; } @@ -62,7 +63,7 @@ function MultiInputComponent(props: MultiInputComponentProps) { return itemList.map((item) => ( )); @@ -133,7 +134,7 @@ function MultiInputComponent(props: MultiInputComponentProps) { const effectiveDisabled = loading ? true : disabled; const loadingIndicator = loading ? : null; - const valueList = value ? value.split(delimiter) : []; + const valueList = value ? String(value).split(delimiter) : []; return ( <> diff --git a/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx b/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx index 032cc1b49..eec9726e6 100644 --- a/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx +++ b/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { render, screen } from '@testing-library/react'; +import { render, screen, within } from '@testing-library/react'; import SingleInputComponent, { SingleInputComponentProps } from './SingleInputComponent'; import { setUnifiedConfig } from '../../util/util'; @@ -132,3 +132,38 @@ describe.each(mockedEntries)('handler endpoint data loading', (entry) => { ); }); }); + +it('should render Select... when value does not exist in autoCompleteFields', () => { + renderFeature({ + value: 'notExistingValue', + controlOptions: { + autoCompleteFields: [ + { + label: 'label1', + value: 'value1', + }, + ], + }, + }); + const inputComponent = screen.getByRole('combobox'); + expect(inputComponent).toBeInTheDocument(); + expect(within(inputComponent).getByText('Select...')).toBeInTheDocument(); +}); + +it.each([ + { value: true, autoCompleteFields: [{ label: 'trueLabel', value: true }] }, + { + value: false, + autoCompleteFields: [{ label: 'falseLabel', value: false }], + }, + { value: 0, autoCompleteFields: [{ label: 'falseLabel', value: '0' }] }, + { value: 0, autoCompleteFields: [{ label: 'falseLabel', value: 0 }] }, +])('should render label with value $value', ({ value, autoCompleteFields }) => { + renderFeature({ + value, + controlOptions: { autoCompleteFields }, + }); + const inputComponent = screen.getByRole('combobox'); + const { label } = autoCompleteFields[0]; + expect(within(inputComponent).getByText(label)).toBeInTheDocument(); +}); diff --git a/ui/src/components/SingleInputComponent/SingleInputComponent.tsx b/ui/src/components/SingleInputComponent/SingleInputComponent.tsx index ee5115c18..cd5e49436 100755 --- a/ui/src/components/SingleInputComponent/SingleInputComponent.tsx +++ b/ui/src/components/SingleInputComponent/SingleInputComponent.tsx @@ -11,7 +11,7 @@ import { AxiosCallType, axiosCallWrapper, generateEndPointUrl } from '../../util import { SelectCommonOptions } from '../../types/globalConfig/entities'; import { filterResponse } from '../../util/util'; import { getValueMapTruthyFalse } from '../../util/considerFalseAndTruthy'; -import { StandardPages } from '../../types/components/shareableTypes'; +import { AcceptableFormValue, StandardPages } from '../../types/components/shareableTypes'; const SelectWrapper = styled(Select)` width: 320px !important; @@ -27,7 +27,7 @@ const StyledDiv = styled.div` } `; -type BasicFormItem = { value: string | number | boolean; label: string }; +type BasicFormItem = { value: AcceptableFormValue; label: string }; type FormItem = | BasicFormItem @@ -39,9 +39,9 @@ type FormItem = export interface SingleInputComponentProps { id?: string; disabled?: boolean; - value: string; + value: AcceptableFormValue; error?: boolean; - handleChange: (field: string, value: string | number | boolean) => void; + handleChange: (field: string, value: string) => void; field: string; dependencyValues?: Record; controlOptions: z.TypeOf & { @@ -74,8 +74,8 @@ function SingleInputComponent(props: SingleInputComponentProps) { hideClearBtn, } = controlOptions; - const handleChange = (e: unknown, obj: { value: string | number | boolean }) => { - restProps.handleChange(field, obj.value); + const handleChange = (e: unknown, obj: { value: AcceptableFormValue }) => { + restProps.handleChange(field, String(obj.value)); }; const Option = createSearchChoice ? ComboBox.Option : Select.Option; const Heading = createSearchChoice ? ComboBox.Heading : Select.Heading; @@ -83,10 +83,16 @@ function SingleInputComponent(props: SingleInputComponentProps) { function generateOptions(items: FormItem[]) { const data: ReactElement[] = []; items.forEach((item) => { - if ('value' in item && item.value && item.label) { - // splunk will mape those when sending post form + if ( + 'value' in item && + item.value !== null && + item.value !== undefined && + item.value !== '' && + item.label + ) { + // splunk will maps those when sending post form // so worth doing it earlier to keep same state before and after post - const itemValue = getValueMapTruthyFalse(item.value, props.page); + const itemValue = String(getValueMapTruthyFalse(item.value, props.page)); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore JSX element type 'Option' does not have any construct or call signatures. data.push(