diff --git a/.changeset/fuzzy-houses-notice.md b/.changeset/fuzzy-houses-notice.md new file mode 100644 index 000000000..8d8d4181d --- /dev/null +++ b/.changeset/fuzzy-houses-notice.md @@ -0,0 +1,6 @@ +--- +"@ensembleui/react-kitchen-sink": patch +"@ensembleui/react-runtime": patch +--- + +Added support for dynamic searchKey in search widget diff --git a/apps/kitchen-sink/src/ensemble/screens/home.yaml b/apps/kitchen-sink/src/ensemble/screens/home.yaml index f0d4a9c8a..daede95f9 100644 --- a/apps/kitchen-sink/src/ensemble/screens/home.yaml +++ b/apps/kitchen-sink/src/ensemble/screens/home.yaml @@ -22,6 +22,8 @@ View: header: title: Header: + inputs: + searchKey: id styles: className: topView diff --git a/apps/kitchen-sink/src/ensemble/widgets/Header.yaml b/apps/kitchen-sink/src/ensemble/widgets/Header.yaml index ac3e00b4f..371bfad75 100644 --- a/apps/kitchen-sink/src/ensemble/widgets/Header.yaml +++ b/apps/kitchen-sink/src/ensemble/widgets/Header.yaml @@ -1,8 +1,9 @@ Widget: inputs: - message + - searchKey onLoad: - executeCode: + executeCode: body: | console.log("hello from header", message); onComplete: @@ -42,7 +43,7 @@ Widget: template: Text: text: ${user.firstName + ' ' + user.lastName} - searchKey: id + searchKey: ${searchKey} onSearch: invokeAPI: name: findUsers diff --git a/packages/runtime/src/widgets/Search.tsx b/packages/runtime/src/widgets/Search.tsx index 3b349f752..d5b4c8771 100644 --- a/packages/runtime/src/widgets/Search.tsx +++ b/packages/runtime/src/widgets/Search.tsx @@ -65,7 +65,7 @@ export const Search: React.FC = ({ }); const { id, rootRef, values } = useRegisterBindings( - { styles, value, ...rest, widgetName, initialValue }, + { styles, value, ...rest, widgetName, initialValue, searchKey }, rest.id, { setValue, @@ -81,12 +81,12 @@ export const Search: React.FC = ({ (option: unknown): string | number => { return get( option, - searchKey - ? [itemTemplate?.name ?? "", searchKey] - : [(itemTemplate?.value || itemTemplate?.name) ?? ""], + values?.searchKey + ? [itemTemplate?.name ?? "", values.searchKey] + : itemTemplate?.value || itemTemplate?.name || "", ) as string | number; }, - [itemTemplate?.name, itemTemplate?.value, searchKey], + [itemTemplate?.name, itemTemplate?.value, values?.searchKey], ); const renderOptions = useMemo(() => { @@ -95,21 +95,25 @@ export const Search: React.FC = ({ let dropdownOptions: JSX.Element[] = []; if (isObject(itemTemplate) && !isEmpty(namedData)) { - const tempOptions = namedData.map((item: unknown, index: number) => { - const optionValue = extractValue(item); - - return ( - - - {EnsembleRuntime.render([itemTemplate.template])} - - - ); - }); + const tempOptions = namedData + .map((item: unknown, index: number) => { + const optionValue = extractValue(item); + + if (!optionValue) return null; + + return ( + + + {EnsembleRuntime.render([itemTemplate.template])} + + + ); + }) + .filter((option) => option !== null) as JSX.Element[]; dropdownOptions = [...dropdownOptions, ...tempOptions]; } diff --git a/packages/runtime/src/widgets/__tests__/Search.test.tsx b/packages/runtime/src/widgets/__tests__/Search.test.tsx new file mode 100644 index 000000000..f0a1194df --- /dev/null +++ b/packages/runtime/src/widgets/__tests__/Search.test.tsx @@ -0,0 +1,145 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import { BrowserRouter } from "react-router-dom"; +import userEvent from "@testing-library/user-event"; +import { EnsembleScreen } from "../../runtime/screen"; +import "../index"; + +describe("Search Widget", () => { + test("dynamic searchKey test", async () => { + render( + , + { wrapper: BrowserRouter }, + ); + + await waitFor(() => { + userEvent.type(screen.getByRole("combobox"), "app"); + }); + + await waitFor(() => { + const optionElements = screen.getAllByRole("option"); + expect(optionElements).toHaveLength(2); + optionElements.forEach((option) => { + expect(option).toBeVisible(); + }); + }); + }); + + test("static searchKey test", async () => { + render( + , + { wrapper: BrowserRouter }, + ); + + await waitFor(() => { + userEvent.type(screen.getByRole("combobox"), "app"); + }); + + await waitFor(() => { + const optionElements = screen.getAllByRole("option"); + expect(optionElements).toHaveLength(2); + optionElements.forEach((option) => { + expect(option).toBeVisible(); + }); + }); + }); + + test("searchKey test with invalid key", async () => { + render( + , + { wrapper: BrowserRouter }, + ); + + await waitFor(() => { + userEvent.type(screen.getByRole("combobox"), "app"); + }); + + await waitFor(() => { + expect(screen.queryByRole("option")).not.toBeInTheDocument(); + }); + }); +});