Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fuzzy-houses-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ensembleui/react-kitchen-sink": patch
"@ensembleui/react-runtime": patch
---

Added support for dynamic searchKey in search widget
2 changes: 2 additions & 0 deletions apps/kitchen-sink/src/ensemble/screens/home.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ View:
header:
title:
Header:
inputs:
searchKey: id

styles:
className: topView
Expand Down
5 changes: 3 additions & 2 deletions apps/kitchen-sink/src/ensemble/widgets/Header.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
Widget:
inputs:
- message
- searchKey
onLoad:
executeCode:
executeCode:
body: |
console.log("hello from header", message);
onComplete:
Expand Down Expand Up @@ -42,7 +43,7 @@ Widget:
template:
Text:
text: ${user.firstName + ' ' + user.lastName}
searchKey: id
searchKey: ${searchKey}
onSearch:
invokeAPI:
name: findUsers
Expand Down
44 changes: 24 additions & 20 deletions packages/runtime/src/widgets/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const Search: React.FC<SearchProps> = ({
});

const { id, rootRef, values } = useRegisterBindings(
{ styles, value, ...rest, widgetName, initialValue },
{ styles, value, ...rest, widgetName, initialValue, searchKey },
rest.id,
{
setValue,
Expand All @@ -81,12 +81,12 @@ export const Search: React.FC<SearchProps> = ({
(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 || "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to remove this here?

) as string | number;
},
[itemTemplate?.name, itemTemplate?.value, searchKey],
[itemTemplate?.name, itemTemplate?.value, values?.searchKey],
);

const renderOptions = useMemo(() => {
Expand All @@ -95,21 +95,25 @@ export const Search: React.FC<SearchProps> = ({
let dropdownOptions: JSX.Element[] = [];

if (isObject(itemTemplate) && !isEmpty(namedData)) {
const tempOptions = namedData.map((item: unknown, index: number) => {
const optionValue = extractValue(item);

return (
<SelectComponent.Option
className={`${values?.id || ""}_option`}
key={`${optionValue}_${index}`}
value={optionValue}
>
<CustomScopeProvider value={item as CustomScope}>
{EnsembleRuntime.render([itemTemplate.template])}
</CustomScopeProvider>
</SelectComponent.Option>
);
});
const tempOptions = namedData
.map((item: unknown, index: number) => {
const optionValue = extractValue(item);

if (!optionValue) return null;

return (
<SelectComponent.Option
className={`${values?.id || ""}_option`}
key={`${optionValue}_${index}`}
value={optionValue}
>
<CustomScopeProvider value={item as CustomScope}>
{EnsembleRuntime.render([itemTemplate.template])}
</CustomScopeProvider>
</SelectComponent.Option>
);
})
.filter((option) => option !== null) as JSX.Element[];

dropdownOptions = [...dropdownOptions, ...tempOptions];
}
Expand Down
145 changes: 145 additions & 0 deletions packages/runtime/src/widgets/__tests__/Search.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<EnsembleScreen
screen={{
name: "test_search",
id: "test_search",
body: {
name: "Search",
properties: {
id: "searchTest",
placeholder: "Enter Search",
searchKey: `\${ensemble.storage.get('searchKey')}`,
"item-template": {
data: [
{ name: "Apple" },
{ name: "Pineapple" },
{ name: "Banana" },
],
name: "fruit",
template: {
name: "Text",
properties: {
text: `\${fruit.name}`,
},
},
},
},
},
onLoad: {
executeCode: "ensemble.storage.set('searchKey', 'name')",
},
}}
/>,
{ 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(
<EnsembleScreen
screen={{
name: "test_search",
id: "test_search",
body: {
name: "Search",
properties: {
id: "searchTest",
placeholder: "Enter Search",
searchKey: "name",
"item-template": {
data: [
{ name: "Apple" },
{ name: "Pineapple" },
{ name: "Banana" },
],
name: "fruit",
template: {
name: "Text",
properties: {
text: `\${fruit.name}`,
},
},
},
},
},
}}
/>,
{ 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(
<EnsembleScreen
screen={{
name: "test_search",
id: "test_search",
body: {
name: "Search",
properties: {
id: "searchTest",
placeholder: "Enter Search",
searchKey: "xyz",
"item-template": {
data: [
{ name: "Apple" },
{ name: "Pineapple" },
{ name: "Banana" },
],
name: "fruit",
template: {
name: "Text",
properties: {
text: `\${fruit.name}`,
},
},
},
},
},
}}
/>,
{ wrapper: BrowserRouter },
);

await waitFor(() => {
userEvent.type(screen.getByRole("combobox"), "app");
});

await waitFor(() => {
expect(screen.queryByRole("option")).not.toBeInTheDocument();
});
});
});
Loading