From 921dd689ae0995bc84eb0f3e437a86fbee111805 Mon Sep 17 00:00:00 2001 From: Naveen-Goud Date: Tue, 17 Sep 2024 13:24:01 +0530 Subject: [PATCH 1/3] fix:updated searchbar component to return result with client side search is enabled only --- .../src/SearchComponent/index.test.tsx | 87 +++++++++++++++++++ .../widgets-old/src/SearchComponent/index.tsx | 16 +++- .../widgets/TableWidgetV2/component/Table.tsx | 2 + .../component/header/actions/index.tsx | 2 + .../TableWidgetV2/component/header/index.tsx | 2 + .../widgets/TableWidgetV2/component/index.tsx | 6 +- .../widgets/TableWidgetV2/widget/index.tsx | 3 +- 7 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx diff --git a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx new file mode 100644 index 000000000000..7819b62acbdc --- /dev/null +++ b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx @@ -0,0 +1,87 @@ +import React from "react"; +import { render, fireEvent, act } from "@testing-library/react"; +import SearchComponent from "../SearchComponent"; +import { debounce } from "lodash"; +import "@testing-library/jest-dom" +// Mocking the debounce function to call the function immediately +jest.mock("lodash", () => ({ + debounce: (fn: any) => fn, +})); + +// Mocking the SVG import to avoid issues with lazy loading in the test environment +jest.mock("../utils/icon-loadables", () => ({ + importSvg: jest.fn().mockReturnValue(() => ), +})); + +describe("SearchComponent", () => { + const onSearchMock = jest.fn(); + + const renderComponent = (props = {}) => { + return render( + + ); + }; + + it("should update localValue and call onSearch when input is changed with client-side search enabled", () => { + const { getByPlaceholderText } = renderComponent({ + enableClientSideSearch: true, + }); + const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; + + act(() => { + fireEvent.change(inputElement, { target: { value: "test" } }); + }); + + expect(inputElement.value).toBe("test"); + expect(onSearchMock).toHaveBeenCalledWith("test"); + }); + + it("should clear the search value and trigger onSearch with an empty value when the cross icon is clicked", () => { + const { getByPlaceholderText, getByTestId } = renderComponent({ + enableClientSideSearch: true, + value: "test", + }); + const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; + const clearButton = getByTestId("cross-icon"); + + act(() => { + fireEvent.click(clearButton); + }); + + expect(inputElement.value).toBe(""); + expect(onSearchMock).toHaveBeenCalledWith(""); + }); + + it("should reset localValue when component receives a new value prop", () => { + const { getByPlaceholderText, rerender } = renderComponent({ + value: "initial", + }); + + const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; + expect(inputElement.value).toBe("initial"); + + rerender(); + + expect(inputElement.value).toBe("updated"); + }); + + it("should clear localValue and call onSearch with an empty value if enableClientSideSearch prop changes", () => { + const { getByPlaceholderText, rerender } = renderComponent({ + enableClientSideSearch: true, + value: "initial", + }); + + const inputElement = getByPlaceholderText("Search...")as HTMLInputElement; + expect(inputElement.value).toBe("initial"); + + rerender(); + + expect(inputElement.value).toBe(""); + expect(onSearchMock).toHaveBeenCalledWith(""); + }); +}); \ No newline at end of file diff --git a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.tsx b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.tsx index 988a5bd91563..a1e516fbb83e 100644 --- a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.tsx +++ b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.tsx @@ -14,6 +14,7 @@ interface SearchProps { value: string; className?: string; autoFocus?: boolean; + enableClientSideSearch?: boolean; } const SearchComponentWrapper = styled.div` @@ -98,6 +99,13 @@ class SearchComponent extends React.Component< if (prevProps.value !== this.props.value) { this.setState({ localValue: this.props.value }); } + + if (prevProps.enableClientSideSearch !== this.props.enableClientSideSearch) { + this.setState({ localValue: "" }, () => { + // Trigger search with an empty value to reset the table + this.props.onSearch(""); + }); + } } handleSearch = ( @@ -107,11 +115,15 @@ class SearchComponent extends React.Component< ) => { const search = event.target.value; this.setState({ localValue: search }); - this.onDebouncedSearch(search); + if (this.props.enableClientSideSearch) { + this.onDebouncedSearch(search); + } }; clearSearch = () => { this.setState({ localValue: "" }); - this.onDebouncedSearch(""); + if (this.props.enableClientSideSearch) { + this.onDebouncedSearch(""); + } }; render() { diff --git a/app/client/src/widgets/TableWidgetV2/component/Table.tsx b/app/client/src/widgets/TableWidgetV2/component/Table.tsx index 8fdeae798f24..1cc596a4fe13 100644 --- a/app/client/src/widgets/TableWidgetV2/component/Table.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/Table.tsx @@ -90,6 +90,7 @@ export interface TableProps { prevPageClick: () => void; serverSidePaginationEnabled: boolean; selectedRowIndex: number; + enableClientSideSearch?: boolean; selectedRowIndices: number[]; disableDrag: () => void; enableDrag: () => void; @@ -405,6 +406,7 @@ export function Table(props: TableProps) { pageNo={props.pageNo} pageOptions={pageOptions} prevPageClick={props.prevPageClick} + enableClientSideSearch={props.enableClientSideSearch} searchKey={props.searchKey} searchTableData={props.searchTableData} serverSidePaginationEnabled={ diff --git a/app/client/src/widgets/TableWidgetV2/component/header/actions/index.tsx b/app/client/src/widgets/TableWidgetV2/component/header/actions/index.tsx index 2547c52837ce..aafb96ded346 100644 --- a/app/client/src/widgets/TableWidgetV2/component/header/actions/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/header/actions/index.tsx @@ -97,6 +97,7 @@ export interface ActionsPropsType { nextPageClick: () => void; prevPageClick: () => void; pageNo: number; + enableClientSideSearch?: boolean; totalRecordsCount?: number; tableData: Array>; tableColumns: ReactTableColumnProps[]; @@ -141,6 +142,7 @@ function Actions(props: ActionsPropsType) { onSearch={props.searchTableData} placeholder="Search..." value={props.searchKey} + enableClientSideSearch={props.enableClientSideSearch} /> )} diff --git a/app/client/src/widgets/TableWidgetV2/component/header/index.tsx b/app/client/src/widgets/TableWidgetV2/component/header/index.tsx index 5b8f14d0a884..f35766bebb21 100644 --- a/app/client/src/widgets/TableWidgetV2/component/header/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/header/index.tsx @@ -12,6 +12,7 @@ function TableHeader(props: ActionsPropsType & BannerPropType) { disabledAddNewRowSave, isAddRowInProgress, onAddNewRowAction, + enableClientSideSearch, ...ActionProps } = props; @@ -26,6 +27,7 @@ function TableHeader(props: ActionsPropsType & BannerPropType) { /> ) : ( void; handleReorderColumn: (columnOrder: string[]) => void; + enableClientSideSearch?: boolean; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any searchTableData: (searchKey: any) => void; @@ -125,6 +126,7 @@ function ReactTableComponent(props: ReactTableComponentProps) { disableDrag, editableCell, editMode, + enableClientSideSearch, filters, handleColumnFreeze, handleReorderColumn, @@ -239,6 +241,7 @@ function ReactTableComponent(props: ReactTableComponentProps) { editMode={editMode} editableCell={editableCell} enableDrag={memoziedEnableDrag} + enableClientSideSearch={props.enableClientSideSearch} filters={filters} handleColumnFreeze={handleColumnFreeze} handleReorderColumn={handleReorderColumn} @@ -338,6 +341,7 @@ export default React.memo(ReactTableComponent, (prev, next) => { prev.allowSorting === next.allowSorting && prev.disabledAddNewRowSave === next.disabledAddNewRowSave && prev.canFreezeColumn === next.canFreezeColumn && - prev.showConnectDataOverlay === next.showConnectDataOverlay + prev.showConnectDataOverlay === next.showConnectDataOverlay && + prev.enableClientSideSearch === next.enableClientSideSearch ); }); diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index 09f45d48be86..e5fea7b68ffa 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -459,7 +459,7 @@ class TableWidgetV2 extends BaseWidget { pageNo: "number", pageSize: "number", isVisible: DefaultAutocompleteDefinitions.isVisible, - searchText: "string", + searchText: generateTypeDef(widget.searchText, extraDefsToDefine), totalRecordsCount: "number", sortOrder: { column: "string", @@ -1254,6 +1254,7 @@ class TableWidgetV2 extends BaseWidget { disabledAddNewRowSave={this.hasInvalidColumnCell()} editMode={this.props.renderMode === RenderModes.CANVAS} editableCell={this.props.editableCell} + enableClientSideSearch={this.props?.enableClientSideSearch} filters={this.props.filters} handleColumnFreeze={this.handleColumnFreeze} handleReorderColumn={this.handleReorderColumn} From b3b6ecf90c8e428671f01f61777e5541d50cd966 Mon Sep 17 00:00:00 2001 From: Naveen-Goud Date: Tue, 1 Oct 2024 15:13:42 +0530 Subject: [PATCH 2/3] fix:Renamed the test case names according to user behavior and expectation --- .../widgets-old/src/SearchComponent/index.test.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx index 7819b62acbdc..985748d4598a 100644 --- a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx +++ b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx @@ -1,8 +1,8 @@ import React from "react"; import { render, fireEvent, act } from "@testing-library/react"; import SearchComponent from "../SearchComponent"; -import { debounce } from "lodash"; import "@testing-library/jest-dom" + // Mocking the debounce function to call the function immediately jest.mock("lodash", () => ({ debounce: (fn: any) => fn, @@ -27,7 +27,7 @@ describe("SearchComponent", () => { ); }; - it("should update localValue and call onSearch when input is changed with client-side search enabled", () => { + it("should allow the user to type in the search box and see results immediately when client-side search is enabled", () => { const { getByPlaceholderText } = renderComponent({ enableClientSideSearch: true, }); @@ -41,7 +41,7 @@ describe("SearchComponent", () => { expect(onSearchMock).toHaveBeenCalledWith("test"); }); - it("should clear the search value and trigger onSearch with an empty value when the cross icon is clicked", () => { + it("should allow the user to clear the search input by clicking the clear button and see updated search results", () => { const { getByPlaceholderText, getByTestId } = renderComponent({ enableClientSideSearch: true, value: "test", @@ -57,7 +57,7 @@ describe("SearchComponent", () => { expect(onSearchMock).toHaveBeenCalledWith(""); }); - it("should reset localValue when component receives a new value prop", () => { + it("should update the search input when the user receives new search criteria from outside the component", () => { const { getByPlaceholderText, rerender } = renderComponent({ value: "initial", }); @@ -70,7 +70,7 @@ describe("SearchComponent", () => { expect(inputElement.value).toBe("updated"); }); - it("should clear localValue and call onSearch with an empty value if enableClientSideSearch prop changes", () => { + it("should clear the search input when the user disables client-side search and see unfiltered results", () => { const { getByPlaceholderText, rerender } = renderComponent({ enableClientSideSearch: true, value: "initial", From aeb03fee5f1c3ebb6ffc0f54eb1e9d9de9d0a566 Mon Sep 17 00:00:00 2001 From: Naveen-Goud Date: Fri, 11 Oct 2024 10:15:37 +0530 Subject: [PATCH 3/3] fix:linting issues --- .../src/SearchComponent/index.test.tsx | 33 ++++++++++++------- .../widgets-old/src/SearchComponent/index.tsx | 10 +++--- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx index 985748d4598a..0e4ae3525993 100644 --- a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx +++ b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx @@ -1,7 +1,7 @@ import React from "react"; import { render, fireEvent, act } from "@testing-library/react"; import SearchComponent from "../SearchComponent"; -import "@testing-library/jest-dom" +import "@testing-library/jest-dom"; // Mocking the debounce function to call the function immediately jest.mock("lodash", () => ({ @@ -23,7 +23,7 @@ describe("SearchComponent", () => { placeholder="Search..." value="" {...props} - /> + />, ); }; @@ -33,9 +33,7 @@ describe("SearchComponent", () => { }); const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; - act(() => { - fireEvent.change(inputElement, { target: { value: "test" } }); - }); + fireEvent.change(inputElement, { target: { value: "test" } }); expect(inputElement.value).toBe("test"); expect(onSearchMock).toHaveBeenCalledWith("test"); @@ -49,9 +47,7 @@ describe("SearchComponent", () => { const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; const clearButton = getByTestId("cross-icon"); - act(() => { - fireEvent.click(clearButton); - }); + fireEvent.click(clearButton); expect(inputElement.value).toBe(""); expect(onSearchMock).toHaveBeenCalledWith(""); @@ -65,7 +61,13 @@ describe("SearchComponent", () => { const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; expect(inputElement.value).toBe("initial"); - rerender(); + rerender( + , + ); expect(inputElement.value).toBe("updated"); }); @@ -76,12 +78,19 @@ describe("SearchComponent", () => { value: "initial", }); - const inputElement = getByPlaceholderText("Search...")as HTMLInputElement; + const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; expect(inputElement.value).toBe("initial"); - rerender(); + rerender( + , + ); expect(inputElement.value).toBe(""); expect(onSearchMock).toHaveBeenCalledWith(""); }); -}); \ No newline at end of file +}); diff --git a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.tsx b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.tsx index a1e516fbb83e..55a98d19d0fc 100644 --- a/app/client/packages/design-system/widgets-old/src/SearchComponent/index.tsx +++ b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.tsx @@ -100,10 +100,12 @@ class SearchComponent extends React.Component< this.setState({ localValue: this.props.value }); } - if (prevProps.enableClientSideSearch !== this.props.enableClientSideSearch) { + if ( + prevProps.enableClientSideSearch !== this.props.enableClientSideSearch + ) { this.setState({ localValue: "" }, () => { // Trigger search with an empty value to reset the table - this.props.onSearch(""); + this.props.onSearch(""); }); } } @@ -116,8 +118,8 @@ class SearchComponent extends React.Component< const search = event.target.value; this.setState({ localValue: search }); if (this.props.enableClientSideSearch) { - this.onDebouncedSearch(search); - } + this.onDebouncedSearch(search); + } }; clearSearch = () => { this.setState({ localValue: "" });