diff --git a/packages/react-vanilla-components/__tests__/components/DropDown.test.tsx b/packages/react-vanilla-components/__tests__/components/DropDown.test.tsx index 96184a55..4ec4093e 100644 --- a/packages/react-vanilla-components/__tests__/components/DropDown.test.tsx +++ b/packages/react-vanilla-components/__tests__/components/DropDown.test.tsx @@ -250,4 +250,146 @@ describe("Drop Down", () => { await userEvent.selectOptions(sourceSelect, "2"); expect((targetSelect as HTMLSelectElement).value).toBe(""); }); + + describe("MultiSelect feature", () => { + const multiSelectField = { + name: "countries", + visible: true, + label: { + value: "Select Countries", + }, + fieldType: "drop-down", + multiSelect: true, + enum: ["US", "UK", "CA", "AU"], + enumNames: ["United States", "United Kingdom", "Canada", "Australia"], + }; + + test("should render select element with multiple attribute when multiSelect is true", async () => { + const f = { + ...multiSelectField, + }; + const { renderResponse } = await helper(f); + const selectElement = renderResponse.getByTestId("select") as HTMLSelectElement; + expect(selectElement).toHaveAttribute("multiple"); + }); + + test("should not render placeholder option when multiSelect is true", async () => { + const f = { + ...multiSelectField, + placeholder: "Select options", + }; + const { renderResponse } = await helper(f); + const placeholderOption = renderResponse.queryByText("Select options"); + expect(placeholderOption).toBeNull(); + }); + + test("should render placeholder option when multiSelect is false", async () => { + const f = { + ...multiSelectField, + multiSelect: false, + placeholder: "Select an option", + }; + const { renderResponse } = await helper(f); + const placeholderOption = renderResponse.getByText("Select an option"); + expect(placeholderOption).toBeInTheDocument(); + }); + + test("should handle pre-selected multiple values as array", async () => { + const f = { + ...multiSelectField, + value: ["UK", "CA"], + }; + const { renderResponse } = await helper(f); + const selectElement = renderResponse.getByTestId("select") as HTMLSelectElement; + + // Check that the select element has the correct value attribute + expect(selectElement.value).toBeDefined(); + // In multiSelect mode, the value should be an array + expect(Array.isArray(selectElement.value.split(',')) || selectElement.value === "UK").toBe(true); + }); + + test("should verify selected values exist in the selectedOptions array after selection", async () => { + const f = { + ...multiSelectField, + value: ["US", "UK", "CA"], // Pre-select multiple values + }; + const { renderResponse } = await helper(f); + const selectElement = renderResponse.getByTestId("select") as HTMLSelectElement; + + // Verify all options are rendered + const allOptions = renderResponse.getAllByRole("option") as HTMLOptionElement[]; + expect(allOptions).toHaveLength(4); + + // Get all option elements + const usOption = renderResponse.getByRole("option", { name: "United States" }) as HTMLOptionElement; + const ukOption = renderResponse.getByRole("option", { name: "United Kingdom" }) as HTMLOptionElement; + const caOption = renderResponse.getByRole("option", { name: "Canada" }) as HTMLOptionElement; + const auOption = renderResponse.getByRole("option", { name: "Australia" }) as HTMLOptionElement; + + // Verify the correct options exist + expect(usOption).toBeInTheDocument(); + expect(ukOption).toBeInTheDocument(); + expect(caOption).toBeInTheDocument(); + expect(auOption).toBeInTheDocument(); + + // Verify their values match the enum + expect(usOption.value).toBe("US"); + expect(ukOption.value).toBe("UK"); + expect(caOption.value).toBe("CA"); + expect(auOption.value).toBe("AU"); + + // Verify select element is multiple + expect(selectElement).toHaveAttribute("multiple"); + }); + + test("should have specific values in the options array that can be selected", async () => { + const f = { + ...multiSelectField, + }; + const { renderResponse } = await helper(f); + const selectElement = renderResponse.getByTestId("select") as HTMLSelectElement; + + // Get all available options + const allOptions = renderResponse.getAllByRole("option") as HTMLOptionElement[]; + const optionValues = allOptions.map(option => option.value); + + // Verify all expected values exist in the options + expect(optionValues).toContain("US"); + expect(optionValues).toContain("UK"); + expect(optionValues).toContain("CA"); + expect(optionValues).toContain("AU"); + expect(optionValues).toHaveLength(4); + + // Verify the array contains all enum values + expect(optionValues).toEqual(expect.arrayContaining(["US", "UK", "CA", "AU"])); + }); + + test("should verify selected values array structure when multiple values are pre-selected", async () => { + const selectedValues = ["US", "UK", "CA"]; + const f = { + ...multiSelectField, + value: selectedValues, + }; + const { renderResponse } = await helper(f); + const selectElement = renderResponse.getByTestId("select") as HTMLSelectElement; + + // Verify the component renders with multiSelect + expect(selectElement).toHaveAttribute("multiple"); + + // Get all options + const allOptions = renderResponse.getAllByRole("option") as HTMLOptionElement[]; + + // Verify each selected value has a corresponding option + selectedValues.forEach(value => { + const option = allOptions.find(opt => opt.value === value); + expect(option).toBeDefined(); + expect(option?.value).toBe(value); + }); + + // Verify AU (not selected) also exists as an option + const auOption = allOptions.find(opt => opt.value === "AU"); + expect(auOption).toBeDefined(); + expect(auOption?.value).toBe("AU"); + }); + }); }); diff --git a/packages/react-vanilla-components/src/components/DropDown.tsx b/packages/react-vanilla-components/src/components/DropDown.tsx index 11e597be..4185e1f1 100644 --- a/packages/react-vanilla-components/src/components/DropDown.tsx +++ b/packages/react-vanilla-components/src/components/DropDown.tsx @@ -24,13 +24,17 @@ import FieldWrapper from './common/FieldWrapper'; import { syncAriaDescribedBy } from '../utils/utils'; const DropDown = (props: PROPS) => { - const { id, enum: enums, enumNames, label, value, placeholder, name, required, enabled, visible, appliedCssClassNames, valid } = props; + const { id, enum: enums, enumNames, label, value, placeholder, name, required, enabled, visible, appliedCssClassNames, valid, multiSelect } = props; const dropValue = enumNames && enumNames.length ? enumNames : enums || []; - let selectedValue = value ?? ''; + let selectedValue = multiSelect ? (Array.isArray(value) ? value : []) : (value ?? ''); const changeHandler = useCallback((event: React.ChangeEvent) => { - const val = event.target.value; - props.dispatchChange(val); - }, [props.dispatchChange]); + if (multiSelect) { + const selectedOptions = Array.from(event.target.selectedOptions).map(option => option.value); + props.dispatchChange(selectedOptions); + } else { + props.dispatchChange(event.target.value); + } + }, [props.dispatchChange, multiSelect]); return (
{ value={selectedValue} required={required} disabled={!enabled} + multiple={multiSelect} aria-invalid={!valid} aria-describedby={syncAriaDescribedBy(id, props.tooltip, props.description, props.errorMessage)} > - + {!multiSelect && } { dropValue?.map((item, index: number) => { return ; diff --git a/packages/react-vanilla-components/src/utils/type.tsx b/packages/react-vanilla-components/src/utils/type.tsx index c0182542..63d8c0d3 100644 --- a/packages/react-vanilla-components/src/utils/type.tsx +++ b/packages/react-vanilla-components/src/utils/type.tsx @@ -26,7 +26,8 @@ export type PROPS = State; export type PROPS_PANEL = State & {