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..0e4ae3525993 --- /dev/null +++ b/app/client/packages/design-system/widgets-old/src/SearchComponent/index.test.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { render, fireEvent, act } from "@testing-library/react"; +import SearchComponent from "../SearchComponent"; +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 allow the user to type in the search box and see results immediately when client-side search is enabled", () => { + const { getByPlaceholderText } = renderComponent({ + enableClientSideSearch: true, + }); + const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; + + fireEvent.change(inputElement, { target: { value: "test" } }); + + expect(inputElement.value).toBe("test"); + expect(onSearchMock).toHaveBeenCalledWith("test"); + }); + + 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", + }); + const inputElement = getByPlaceholderText("Search...") as HTMLInputElement; + const clearButton = getByTestId("cross-icon"); + + fireEvent.click(clearButton); + + expect(inputElement.value).toBe(""); + expect(onSearchMock).toHaveBeenCalledWith(""); + }); + + it("should update the search input when the user receives new search criteria from outside the component", () => { + 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 the search input when the user disables client-side search and see unfiltered results", () => { + 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(""); + }); +}); 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 ab51942ff46b..77f94514e3a7 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,15 @@ 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 = ( @@ -108,11 +118,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 5caf25e08e38..5cb1bfe6b3d4 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; @@ -414,6 +415,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 faf5a4316e74..5cf8dac0b019 100644 --- a/app/client/src/widgets/TableWidgetV2/component/header/actions/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/header/actions/index.tsx @@ -98,6 +98,7 @@ export interface ActionsPropsType { nextPageClick: () => void; prevPageClick: () => void; pageNo: number; + enableClientSideSearch?: boolean; totalRecordsCount?: number; tableData: Array>; tableColumns: ReactTableColumnProps[]; @@ -142,6 +143,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, @@ -240,6 +242,7 @@ function ReactTableComponent(props: ReactTableComponentProps) { editMode={editMode} editableCell={editableCell} enableDrag={memoziedEnableDrag} + enableClientSideSearch={props.enableClientSideSearch} filters={filters} handleColumnFreeze={handleColumnFreeze} handleReorderColumn={handleReorderColumn} @@ -339,6 +342,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 b83902333947..d41e0d29368e 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -454,7 +454,7 @@ class TableWidgetV2 extends BaseWidget { pageNo: "number", pageSize: "number", isVisible: DefaultAutocompleteDefinitions.isVisible, - searchText: "string", + searchText: generateTypeDef(widget.searchText, extraDefsToDefine), totalRecordsCount: "number", sortOrder: { column: "string", @@ -1259,6 +1259,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}