From 017bd9e3f3f33f16a3ff399d604ccda0ef074526 Mon Sep 17 00:00:00 2001 From: sagar davara Date: Sat, 8 Feb 2025 13:16:35 +0530 Subject: [PATCH 01/10] fix: keep selected options in search multiselect --- .changeset/nasty-cats-provide.md | 6 + .../src/ensemble/screens/help.yaml | 48 ++++++ .../runtime/src/widgets/Form/MultiSelect.tsx | 12 +- .../Form/__tests__/MultiSelect.test.tsx | 149 ++++++++++++++++++ 4 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 .changeset/nasty-cats-provide.md diff --git a/.changeset/nasty-cats-provide.md b/.changeset/nasty-cats-provide.md new file mode 100644 index 000000000..09ee3d14e --- /dev/null +++ b/.changeset/nasty-cats-provide.md @@ -0,0 +1,6 @@ +--- +"@ensembleui/react-kitchen-sink": patch +"@ensembleui/react-runtime": patch +--- + +Keep selected options in search multiselect diff --git a/apps/kitchen-sink/src/ensemble/screens/help.yaml b/apps/kitchen-sink/src/ensemble/screens/help.yaml index e90cb49a7..2a639819a 100644 --- a/apps/kitchen-sink/src/ensemble/screens/help.yaml +++ b/apps/kitchen-sink/src/ensemble/screens/help.yaml @@ -1,4 +1,20 @@ View: + onLoad: + executeCode: | + ensemble.storage.set('testlistoptions', [ + { label: 'William Smith', value: 'william_smith' }, + { label: 'Evelyn Johnson', value: 'evelyn_johnson' }, + { label: 'Liam Brown', value: 'liam_brown' }, + { label: 'Bella Davis', value: 'bella_davis' }, + { label: 'James Wilson', value: 'james_wilson' }, + { label: 'Zachary Taylor', value: 'zachary_taylor' }, + { label: 'Nolan Martinez', value: 'nolan_martinez' }, + { label: 'Emma Thompson', value: 'emma_thompson' }, + { label: 'Oliver White', value: 'oliver_white' }, + { label: 'Sophia Lee', value: 'sophia_lee' }, + ]) + ensemble.storage.set('testoptions', []) + styles: scrollableView: true @@ -28,6 +44,31 @@ View: inputs: input1: "hello" input2: "world" + + - Button: + label: toggle modal + onTap: + showDialog: + options: + minWidth: 500px + body: + MultiSelect: + label: test + data: ${ensemble.storage.get('testoptions')} + labelKey: label + valueKey: value + value: ${ensemble.storage.get('testselect') || []} + onChange: + executeCode: | + ensemble.storage.set('testselect', option) + onSearch: + executeCode: | + const tempOptions = ensemble.storage.get('testlistoptions') + const filteredResult = tempOptions.filter(option => + option.label.toLowerCase().startsWith(search.toLowerCase()) + ) + ensemble.storage.set('testoptions', filteredResult) + - Text: styles: names: heading-1 @@ -75,3 +116,10 @@ View: styles: maxWidth: 500px source: https://img.huffingtonpost.com/asset/5ab4d4ac2000007d06eb2c56.jpeg?cache=sih0jwle4e&ops=1910_1000 + +API: + getProducts: + method: GET + inputs: + - search + uri: "https://dummyjson.com/users/search?q=${search}&limit=10" diff --git a/packages/runtime/src/widgets/Form/MultiSelect.tsx b/packages/runtime/src/widgets/Form/MultiSelect.tsx index e3aa55984..45381cb34 100644 --- a/packages/runtime/src/widgets/Form/MultiSelect.tsx +++ b/packages/runtime/src/widgets/Form/MultiSelect.tsx @@ -123,7 +123,7 @@ const MultiSelect: React.FC = (props) => { // load data and items useEffect(() => { - const tempOptions: MultiSelectOption[] = []; + const tempOptions: MultiSelectOption[] = values?.selectedValues || []; if (isArray(rawData)) { tempOptions.push( @@ -152,7 +152,15 @@ const MultiSelect: React.FC = (props) => { ); } - setOptions(tempOptions); + const uniqueOptions = tempOptions.reduce((acc, current) => { + const x = acc.find((item) => item.value === current.value); + if (!x) { + return acc.concat([current]); + } + return acc; + }, [] as MultiSelectOption[]); + + setOptions(uniqueOptions); }, [rawData, values?.labelKey, values?.valueKey, values?.items]); // handle form instance diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index d1c692922..5561753fb 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -2,8 +2,30 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom"; import userEvent from "@testing-library/user-event"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { type ReactNode } from "react"; +import { BrowserRouter } from "react-router-dom"; import { Form } from "../../index"; import { FormTestWrapper } from "./__shared__/fixtures"; +import { EnsembleScreen } from "../../../runtime/screen"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); + +interface BrowserRouterProps { + children: ReactNode; +} + +const BrowserRouterWrapper = ({ children }: BrowserRouterProps) => ( + + {children} + +); const defaultFormButton = [ { @@ -645,5 +667,132 @@ describe("MultiSelect Widget", () => { expect(screen.getByText("Anothe...")).toBeInTheDocument(); }); }); + + test.only("multiselect with search action", async () => { + render( + + option.label.toLowerCase().startsWith(search.toLowerCase()) + ) + ensemble.storage.set('testoptions', filteredResult) + `, + }, + }, + }, + { + Button: { + label: "Close modal", + onTap: { + executeCode: "ensemble.closeAllDialogs()", + }, + }, + }, + ], + }, + }, + }, + }, + }, + }, + apis: [{ name: "testCache", method: "GET" }], + }} + />, + { wrapper: BrowserRouterWrapper }, + ); + + const showDialogButton = screen.getByText("Show Dialog"); + fireEvent.click(showDialogButton); + + // verify the modal + const modalTitle = screen.getByText("This is modal"); + await waitFor(() => { + expect(modalTitle).toBeInTheDocument(); + }); + + // search by type + await userEvent.click(screen.getByRole("combobox")); + await userEvent.type(document.querySelector("input") as HTMLElement, "B"); + + // check for updated option list + await waitFor(() => { + expect( + screen.getByText("Bella Davis", { + selector: ".ant-select-item-option-content", + }), + ).toBeInTheDocument(); + }); + + // click the option + await userEvent.click( + screen.getByText("Bella Davis", { + selector: ".ant-select-item-option-content", + }), + ); + + // Wait for the combobox to reflect the selected values + await waitFor(() => { + expect( + screen.getByText("Bella Davis", { + selector: ".ant-select-selection-item-content", + }), + ).toBeVisible(); + }); + + // close the modal + const closeDialogButton = screen.getByText("Close modal"); + fireEvent.click(closeDialogButton); + + await waitFor(() => { + expect(modalTitle).not.toBeInTheDocument(); + }); + + //TODO: again open the modal and repeat same process with different option + }); }); /* eslint-enable react/no-children-prop */ From 349e9195ece61d5bf7c669ce2fab4c84b2dbe0ef Mon Sep 17 00:00:00 2001 From: sagar davara Date: Mon, 10 Feb 2025 18:50:13 +0530 Subject: [PATCH 02/10] fix: update test case --- .../Form/__tests__/MultiSelect.test.tsx | 90 ++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index 5561753fb..ac9ab8a6b 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -668,7 +668,7 @@ describe("MultiSelect Widget", () => { }); }); - test.only("multiselect with search action", async () => { + test("multiselect with search action", async () => { render( { onLoad: { executeCode: { body: ` - ensemble.storage.set('testlistoptions', [ + ensemble.storage.set('test_list_options', [ { label: 'William Smith', value: 'william_smith' }, { label: 'Evelyn Johnson', value: 'evelyn_johnson' }, { label: 'Liam Brown', value: 'liam_brown' }, @@ -689,7 +689,7 @@ describe("MultiSelect Widget", () => { { label: 'Oliver White', value: 'oliver_white' }, { label: 'Sophia Lee', value: 'sophia_lee' }, ]) - ensemble.storage.set('testoptions', []) + ensemble.storage.set('test_options', []) `, }, }, @@ -703,24 +703,27 @@ describe("MultiSelect Widget", () => { Column: { children: [ { - Text: { - text: "This is modal", - }, + Text: { text: "This is modal" }, }, { MultiSelect: { - data: "${ensemble.storage.get('testoptions')}", + data: `\${ensemble.storage.get('test_options')}`, labelKey: "label", valueKey: "value", - value: - "${ensemble.storage.get('testselect') || []}", + value: `\${ensemble.storage.get('test_select') || []}`, + onChange: { + executeCode: ` + ensemble.storage.set('test_select', option) + console.log('test_select', ensemble.storage.get('test_select')) + `, + }, onSearch: { executeCode: ` - const tempOptions = ensemble.storage.get('testlistoptions') + const tempOptions = ensemble.storage.get('test_list_options') const filteredResult = tempOptions.filter(option => option.label.toLowerCase().startsWith(search.toLowerCase()) ) - ensemble.storage.set('testoptions', filteredResult) + ensemble.storage.set('test_options', filteredResult) `, }, }, @@ -746,53 +749,56 @@ describe("MultiSelect Widget", () => { { wrapper: BrowserRouterWrapper }, ); - const showDialogButton = screen.getByText("Show Dialog"); - fireEvent.click(showDialogButton); + const option = { selector: ".ant-select-item-option-content" }; + const selected = { selector: ".ant-select-selection-item-content" }; + + userEvent.click(screen.getByText("Show Dialog")); - // verify the modal - const modalTitle = screen.getByText("This is modal"); await waitFor(() => { - expect(modalTitle).toBeInTheDocument(); + expect(screen.getByText("This is modal")).toBeInTheDocument(); }); - // search by type - await userEvent.click(screen.getByRole("combobox")); - await userEvent.type(document.querySelector("input") as HTMLElement, "B"); + userEvent.type(screen.getByRole("combobox"), "Bella"); - // check for updated option list await waitFor(() => { - expect( - screen.getByText("Bella Davis", { - selector: ".ant-select-item-option-content", - }), - ).toBeInTheDocument(); + expect(screen.getByText("Bella Davis", option)).toBeInTheDocument(); }); - // click the option - await userEvent.click( - screen.getByText("Bella Davis", { - selector: ".ant-select-item-option-content", - }), - ); + userEvent.click(screen.getByText("Bella Davis", option)); - // Wait for the combobox to reflect the selected values await waitFor(() => { - expect( - screen.getByText("Bella Davis", { - selector: ".ant-select-selection-item-content", - }), - ).toBeVisible(); + expect(screen.getByText("Bella Davis", selected)).toBeVisible(); }); - // close the modal - const closeDialogButton = screen.getByText("Close modal"); - fireEvent.click(closeDialogButton); + userEvent.click(screen.getByText("Close modal")); + + // Open 2nd time to check if the selected values are retained + + userEvent.click(screen.getByText("Show Dialog")); + + userEvent.type(screen.getByRole("combobox"), "Sophia"); + + await waitFor(() => { + expect(screen.getByText("Sophia Lee", option)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByText("Sophia Lee", option)); await waitFor(() => { - expect(modalTitle).not.toBeInTheDocument(); + expect(screen.queryByText("Bella Davis", selected)).toBeVisible(); + expect(screen.queryByText("Sophia Lee", selected)).toBeVisible(); }); - //TODO: again open the modal and repeat same process with different option + userEvent.click(screen.getByText("Close modal")); + + // Open 3rd time to check if the selected values are retained + + userEvent.click(screen.getByText("Show Dialog")); + + await waitFor(() => { + expect(screen.queryByText("Bella Davis", selected)).toBeVisible(); + expect(screen.queryByText("Sophia Lee", selected)).toBeVisible(); + }); }); }); /* eslint-enable react/no-children-prop */ From ec94ef4e42cd8e6f22a6793fe62ecc9834e7fb29 Mon Sep 17 00:00:00 2001 From: sagar davara Date: Mon, 10 Feb 2025 20:40:09 +0530 Subject: [PATCH 03/10] fix: updated testcase --- .../runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index ac9ab8a6b..eee3c0e8e 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -743,7 +743,6 @@ describe("MultiSelect Widget", () => { }, }, }, - apis: [{ name: "testCache", method: "GET" }], }} />, { wrapper: BrowserRouterWrapper }, @@ -799,6 +798,6 @@ describe("MultiSelect Widget", () => { expect(screen.queryByText("Bella Davis", selected)).toBeVisible(); expect(screen.queryByText("Sophia Lee", selected)).toBeVisible(); }); - }); + }, 10000); }); /* eslint-enable react/no-children-prop */ From c42e870c964014c64ad844840accfb5622fa2392 Mon Sep 17 00:00:00 2001 From: sagar davara Date: Wed, 19 Mar 2025 16:15:08 +0530 Subject: [PATCH 04/10] fix: optimized the reduce with uniqBy --- packages/runtime/src/widgets/Form/MultiSelect.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/runtime/src/widgets/Form/MultiSelect.tsx b/packages/runtime/src/widgets/Form/MultiSelect.tsx index 45381cb34..7f951e3df 100644 --- a/packages/runtime/src/widgets/Form/MultiSelect.tsx +++ b/packages/runtime/src/widgets/Form/MultiSelect.tsx @@ -22,6 +22,7 @@ import { isObject, isString, toNumber, + uniqBy, } from "lodash-es"; import { useDebounce } from "react-use"; import { WidgetRegistry } from "../../registry"; @@ -152,13 +153,7 @@ const MultiSelect: React.FC = (props) => { ); } - const uniqueOptions = tempOptions.reduce((acc, current) => { - const x = acc.find((item) => item.value === current.value); - if (!x) { - return acc.concat([current]); - } - return acc; - }, [] as MultiSelectOption[]); + const uniqueOptions = uniqBy(tempOptions, "value"); setOptions(uniqueOptions); }, [rawData, values?.labelKey, values?.valueKey, values?.items]); From a08b29401a2633e5577bf7c472fcc62296b3d4a1 Mon Sep 17 00:00:00 2001 From: sagar davara Date: Wed, 19 Mar 2025 16:42:35 +0530 Subject: [PATCH 05/10] fix: increase test timer --- .../runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index eee3c0e8e..6a52e3043 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -798,6 +798,6 @@ describe("MultiSelect Widget", () => { expect(screen.queryByText("Bella Davis", selected)).toBeVisible(); expect(screen.queryByText("Sophia Lee", selected)).toBeVisible(); }); - }, 10000); + }, 15000); }); /* eslint-enable react/no-children-prop */ From 22a6a81bce4af1ab8d57cb6754e51974654e4df7 Mon Sep 17 00:00:00 2001 From: sagar davara Date: Wed, 19 Mar 2025 17:01:26 +0530 Subject: [PATCH 06/10] fix: remove time from test --- .../runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index 6a52e3043..e982efd46 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -798,6 +798,6 @@ describe("MultiSelect Widget", () => { expect(screen.queryByText("Bella Davis", selected)).toBeVisible(); expect(screen.queryByText("Sophia Lee", selected)).toBeVisible(); }); - }, 15000); + }); }); /* eslint-enable react/no-children-prop */ From 67fe06912a1585a6f50a4289b49ddb866400c759 Mon Sep 17 00:00:00 2001 From: sagar davara Date: Wed, 19 Mar 2025 17:14:33 +0530 Subject: [PATCH 07/10] fix: increase timer for test --- .../runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index e982efd46..8e3875fee 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -798,6 +798,6 @@ describe("MultiSelect Widget", () => { expect(screen.queryByText("Bella Davis", selected)).toBeVisible(); expect(screen.queryByText("Sophia Lee", selected)).toBeVisible(); }); - }); + }, 20000); }); /* eslint-enable react/no-children-prop */ From da17474a249f1f5b029b03ce7d748e10b6236ddd Mon Sep 17 00:00:00 2001 From: sagar davara Date: Sat, 22 Mar 2025 10:03:15 +0530 Subject: [PATCH 08/10] wip: debugging testcase --- .../widgets/Form/__tests__/MultiSelect.test.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index 8e3875fee..d4df174c9 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -795,8 +795,20 @@ describe("MultiSelect Widget", () => { userEvent.click(screen.getByText("Show Dialog")); await waitFor(() => { - expect(screen.queryByText("Bella Davis", selected)).toBeVisible(); - expect(screen.queryByText("Sophia Lee", selected)).toBeVisible(); + expect(screen.getByText("This is modal")).toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByText("Bella Davis", { + selector: ".ant-select-selection-item-content", + }), + ).toBeVisible(); + expect( + screen.getByText("Sophia Lee", { + selector: ".ant-select-selection-item-content", + }), + ).toBeVisible(); }); }, 20000); }); From bbe89ebbb35b0995812d0cf4fd1d4419ad76fd47 Mon Sep 17 00:00:00 2001 From: sagar davara Date: Sat, 22 Mar 2025 12:41:37 +0530 Subject: [PATCH 09/10] wip: update test case --- .../Form/__tests__/MultiSelect.test.tsx | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index d4df174c9..fe04e4806 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -668,7 +668,7 @@ describe("MultiSelect Widget", () => { }); }); - test("multiselect with search action", async () => { + test.only("multiselect with search action", async () => { render( { userEvent.click(screen.getByText("Show Dialog")); - await waitFor(() => { - expect(screen.getByText("This is modal")).toBeInTheDocument(); + const element = screen.getByText("Bella Davis", { + selector: ".ant-select-selection-item-content", }); + expect(element).toBeInTheDocument(); + await waitFor(() => { - expect( - screen.getByText("Bella Davis", { - selector: ".ant-select-selection-item-content", - }), - ).toBeVisible(); - expect( - screen.getByText("Sophia Lee", { - selector: ".ant-select-selection-item-content", - }), - ).toBeVisible(); + expect(element).toBeInTheDocument(); }); - }, 20000); + }, 10000); }); /* eslint-enable react/no-children-prop */ From 812fcd524696fee989d2354bd49ae053046a4f8f Mon Sep 17 00:00:00 2001 From: sagar davara Date: Sat, 22 Mar 2025 12:55:04 +0530 Subject: [PATCH 10/10] fix: update the multiselect test case --- .../widgets/Form/__tests__/MultiSelect.test.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index fe04e4806..4a1164b9c 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -794,14 +794,17 @@ describe("MultiSelect Widget", () => { userEvent.click(screen.getByText("Show Dialog")); - const element = screen.getByText("Bella Davis", { - selector: ".ant-select-selection-item-content", - }); - - expect(element).toBeInTheDocument(); - await waitFor(() => { - expect(element).toBeInTheDocument(); + expect( + screen.getByText("Bella Davis", { + selector: ".ant-select-selection-item-content", + }), + ).toBeInTheDocument(); + expect( + screen.getByText("Sophia Lee", { + selector: ".ant-select-selection-item-content", + }), + ).toBeInTheDocument(); }); }, 10000); });