From 3c3b120b9b5174a590fd6d6ec3478097086df86a Mon Sep 17 00:00:00 2001 From: gabrielburnworth Date: Mon, 13 Apr 2020 18:15:11 -0700 Subject: [PATCH] refactor search fields --- frontend/css/farm_designer/farm_designer.scss | 95 +++++++++---------- .../farm_designer/farm_designer_panels.scss | 11 ++- frontend/farm_designer/designer_panel.tsx | 11 +-- .../farm_designer/farm_events/farm_events.tsx | 15 ++- .../farm_designer/map/__tests__/util_test.ts | 17 ++-- frontend/farm_designer/map/util.ts | 8 +- .../plants/__tests__/crop_catalog_test.tsx | 9 +- .../plants/__tests__/plant_inventory_test.tsx | 6 +- .../farm_designer/plants/crop_catalog.tsx | 24 ++--- .../farm_designer/plants/plant_inventory.tsx | 10 +- .../__tests__/group_list_panel_test.tsx | 4 +- .../criteria/__tests__/component_test.tsx | 4 +- .../point_groups/criteria/component.tsx | 12 +-- .../point_groups/criteria/show.tsx | 2 +- .../point_groups/group_list_panel.tsx | 12 +-- .../points/__tests__/point_inventory_test.tsx | 4 +- frontend/farm_designer/points/point_info.tsx | 1 + .../farm_designer/points/point_inventory.tsx | 10 +- .../__tests__/saved_gardens_test.tsx | 14 +-- .../saved_gardens/saved_gardens.tsx | 14 +-- .../tools/__tests__/index_test.tsx | 4 +- frontend/farm_designer/tools/index.tsx | 10 +- .../weeds/__tests__/weeds_inventory_test.tsx | 4 +- .../farm_designer/weeds/weeds_inventory.tsx | 10 +- .../zones/__tests__/zones_inventory_test.tsx | 4 +- .../farm_designer/zones/zones_inventory.tsx | 10 +- frontend/folders/__tests__/component_test.tsx | 5 +- frontend/folders/component.tsx | 15 +-- frontend/logs/__tests__/index_test.tsx | 4 +- frontend/logs/index.tsx | 14 +-- .../regimens/list/__tests__/index_test.tsx | 7 +- frontend/regimens/list/index.tsx | 23 ++--- frontend/ui/__tests__/search_field_test.tsx | 43 +++++++++ frontend/ui/search_field.tsx | 30 ++++++ 34 files changed, 244 insertions(+), 222 deletions(-) create mode 100644 frontend/ui/__tests__/search_field_test.tsx create mode 100644 frontend/ui/search_field.tsx diff --git a/frontend/css/farm_designer/farm_designer.scss b/frontend/css/farm_designer/farm_designer.scss index 7dec70f81..4bfb16e78 100644 --- a/frontend/css/farm_designer/farm_designer.scss +++ b/frontend/css/farm_designer/farm_designer.scss @@ -120,46 +120,57 @@ .thin-search-wrapper { width: 100%; - .text-input-wrapper { - position: relative; - margin: 1rem; - border-bottom: 1px solid $dark_gray; - &:before, - &:after { - content: ""; + .thin-search { + .spinner-container { position: absolute; - bottom: 0; - background: $dark_gray; - width: 1px; - height: 3px; - } - &:before { - left: 0; - } - &:after { + top: 0; right: 0; + width: 2rem; + height: 2rem; + padding: 0; + margin-right: 1rem; } - i { - font-size: 1.5rem; + .text-input-wrapper { + position: relative; + margin: 1rem; + border-bottom: 1px solid $dark_gray; + &:before, + &:after { + content: ""; + position: absolute; + bottom: 0; + background: $dark_gray; + width: 1px; + height: 3px; + } + &:before { + left: 0; + } + &:after { + right: 0; + } + i { + font-size: 1.5rem; + } + .fa-search { + position: absolute; + top: 0.8rem; + left: 1rem; + cursor: default !important; + } } - .fa-search { - position: absolute; - top: 0.8rem; - left: 1rem; - cursor: default !important; - } - } - input { - background: transparent; - box-shadow: none !important; - padding-left: 3rem !important; - font-size: 1.4rem !important; - &:active, - &:focus { - background: transparent !important; - } - &::-webkit-input-placeholder { - color: $placeholder_gray; + input { + background: transparent; + box-shadow: none !important; + padding-left: 3rem !important; + font-size: 1.4rem !important; + &:active, + &:focus { + background: transparent !important; + } + &::-webkit-input-placeholder { + color: $placeholder_gray; + } } } } @@ -289,18 +300,6 @@ } } -.thin-search { - .spinner-container { - position: absolute; - top: 0; - right: 0; - width: 2rem; - height: 2rem; - padding: 0; - margin-right: 1rem; - } -} - .hovered-plant-copy { cursor: pointer; transform-origin: center; diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index a778aef14..447bf5174 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -925,6 +925,10 @@ display: inline; text-transform: uppercase; } + input[type="text"] { + width: 50%; + height: 2rem; + } } .point-type-checkboxes { .point-type-section { @@ -1043,6 +1047,11 @@ margin-left: 1rem; font-size: 1.4rem; } + .filter-search { + .bp3-popover-wrapper { + margin-left: 0; + } + } .row { margin-left: 0; } @@ -1074,7 +1083,7 @@ } p { display: inline; - margin-left: 1rem; + margin-left: 0.5rem; } } } diff --git a/frontend/farm_designer/designer_panel.tsx b/frontend/farm_designer/designer_panel.tsx index dd369abf5..6270c771b 100644 --- a/frontend/farm_designer/designer_panel.tsx +++ b/frontend/farm_designer/designer_panel.tsx @@ -81,21 +81,12 @@ interface DesignerPanelTopProps { onClick?(): void; title?: string; children?: React.ReactNode; - noIcon?: boolean; } export const DesignerPanelTop = (props: DesignerPanelTopProps) => { const withBtn = !!props.linkTo || !!props.onClick; return
-
-
- {!props.noIcon && - } - - {props.children} - -
-
+ {props.children} {props.onClick &&
(item: CalendarOccurrence) => item.heading.toLowerCase().includes(term) @@ -105,14 +106,12 @@ export class PureFarmEvents - - this.setState({ searchTerm: e.currentTarget.value })} - placeholder={t("Search your events...")} /> + title={t("Add event")}> + } + placeholder={t("Search your events...")} + onChange={searchTerm => this.setState({ searchTerm })} />
diff --git a/frontend/farm_designer/map/__tests__/util_test.ts b/frontend/farm_designer/map/__tests__/util_test.ts index 39b61cc05..fa0a3e0a0 100644 --- a/frontend/farm_designer/map/__tests__/util_test.ts +++ b/frontend/farm_designer/map/__tests__/util_test.ts @@ -4,11 +4,6 @@ jest.mock("../../../history", () => ({ history: { getCurrentLocation: () => ({ pathname: mockPath }) } })); -let mockGardenOpen = true; -jest.mock("../../saved_gardens/saved_gardens", () => ({ - savedGardenOpen: () => mockGardenOpen, -})); - import { round, translateScreenToGarden, @@ -23,6 +18,7 @@ import { cursorAtPlant, allowInteraction, allowGroupAreaInteraction, + savedGardenOpen, } from "../util"; import { McuParams } from "farmbot"; import { @@ -374,17 +370,22 @@ describe("getMode()", () => { expect(getMode()).toEqual(Mode.weeds); mockPath = "/app/designer/weeds/add"; expect(getMode()).toEqual(Mode.createWeed); - mockPath = "/app/designer/gardens"; - mockGardenOpen = true; + mockPath = "/app/designer/gardens/1"; expect(getMode()).toEqual(Mode.templateView); mockPath = "/app/designer/groups/1"; expect(getMode()).toEqual(Mode.editGroup); mockPath = ""; - mockGardenOpen = false; expect(getMode()).toEqual(Mode.none); }); }); +describe("savedGardenOpen", () => { + it("is open", () => { + const result = savedGardenOpen(["", "", "", "gardens", "4", ""]); + expect(result).toEqual(4); + }); +}); + describe("getGardenCoordinates()", () => { beforeEach(() => { Object.defineProperty(document, "querySelector", { diff --git a/frontend/farm_designer/map/util.ts b/frontend/farm_designer/map/util.ts index 64f8299f7..8ec9b29d6 100644 --- a/frontend/farm_designer/map/util.ts +++ b/frontend/farm_designer/map/util.ts @@ -7,7 +7,6 @@ import { } from "./interfaces"; import { trim } from "../../util"; import { history, getPathArray } from "../../history"; -import { savedGardenOpen } from "../saved_gardens/saved_gardens"; /* * Farm Designer Map Utilities @@ -293,6 +292,7 @@ export const getMode = (): Mode => { if ((pathArray[3] === "groups" || pathArray[3] === "zones") && pathArray[4]) { return Mode.editGroup; } if (pathArray[6] === "add") { return Mode.clickToAdd; } + if (savedGardenOpen(pathArray)) { return Mode.templateView; } if (!isNaN(parseInt(pathArray.slice(-1)[0]))) { return Mode.editPlant; } if (pathArray[5] === "edit") { return Mode.editPlant; } if (pathArray[6] === "edit") { return Mode.editPlant; } @@ -307,11 +307,15 @@ export const getMode = (): Mode => { if (pathArray[4] === "add") { return Mode.createWeed; } return Mode.weeds; } - if (savedGardenOpen(pathArray)) { return Mode.templateView; } } return Mode.none; }; +/** Check if a SavedGarden is currently open (URL approach). */ +export const savedGardenOpen = (pathArray: string[]) => + pathArray[3] === "gardens" && parseInt(pathArray[4]) > 0 + ? parseInt(pathArray[4]) : false; + export const getZoomLevelFromMap = (map: Element) => parseFloat((window.getComputedStyle(map).transform || "(1").split("(")[1]); diff --git a/frontend/farm_designer/plants/__tests__/crop_catalog_test.tsx b/frontend/farm_designer/plants/__tests__/crop_catalog_test.tsx index b4eea09d9..39d6ede51 100644 --- a/frontend/farm_designer/plants/__tests__/crop_catalog_test.tsx +++ b/frontend/farm_designer/plants/__tests__/crop_catalog_test.tsx @@ -18,6 +18,7 @@ import { history } from "../../../history"; import { fakeCropLiveSearchResult, } from "../../../__test_support__/fake_crop_search_result"; +import { SearchField } from "../../../ui/search_field"; describe("", () => { const fakeProps = (): CropCatalogProps => { @@ -40,9 +41,7 @@ describe("", () => { it("handles search term change", () => { const p = fakeProps(); const wrapper = shallow(); - wrapper.find("input").first().simulate("change", { - currentTarget: { value: "apple" } - }); + wrapper.find(SearchField).simulate("change", "apple"); expect(p.dispatch).toHaveBeenCalledWith({ payload: "apple", type: Actions.SEARCH_QUERY_CHANGE @@ -69,7 +68,7 @@ describe("", () => { p.cropSearchQuery = "abc"; p.cropSearchInProgress = true; p.cropSearchResults = [fakeCropLiveSearchResult()]; - const wrapper = shallow(); - expect(wrapper.find("Spinner").length).toEqual(1); + const wrapper = mount(); + expect(wrapper.find(".spinner").length).toEqual(1); }); }); diff --git a/frontend/farm_designer/plants/__tests__/plant_inventory_test.tsx b/frontend/farm_designer/plants/__tests__/plant_inventory_test.tsx index 7a53073f6..496bdb96b 100644 --- a/frontend/farm_designer/plants/__tests__/plant_inventory_test.tsx +++ b/frontend/farm_designer/plants/__tests__/plant_inventory_test.tsx @@ -9,6 +9,7 @@ import { import { mount, shallow } from "enzyme"; import { fakePlant } from "../../../__test_support__/fake_state/resources"; import { fakeState } from "../../../__test_support__/fake_state"; +import { SearchField } from "../../../ui/search_field"; describe("", () => { const fakeProps = (): PlantInventoryProps => ({ @@ -31,11 +32,10 @@ describe("", () => { expect(wrapper.html()).toContain("/app/designer/plants/crop_search"); }); - it("updates search term", () => { + it("changes search term", () => { const wrapper = shallow(); expect(wrapper.state().searchTerm).toEqual(""); - wrapper.find("input").first().simulate("change", - { currentTarget: { value: "mint" } }); + wrapper.find(SearchField).simulate("change", "mint"); expect(wrapper.state().searchTerm).toEqual("mint"); }); }); diff --git a/frontend/farm_designer/plants/crop_catalog.tsx b/frontend/farm_designer/plants/crop_catalog.tsx index 7caa51051..cd57d0bf3 100644 --- a/frontend/farm_designer/plants/crop_catalog.tsx +++ b/frontend/farm_designer/plants/crop_catalog.tsx @@ -15,6 +15,7 @@ import { } from "../designer_panel"; import { t } from "../../i18next_wrapper"; import { Panel } from "../panel_header"; +import { SearchField } from "../../ui/search_field"; export function mapStateToProps(props: Everything): CropCatalogProps { const { cropSearchQuery, cropSearchInProgress, cropSearchResults @@ -34,8 +35,7 @@ export class RawCropCatalog extends React.Component { this.props.openfarmSearch(searchTerm)(this.props.dispatch); }, 500); - handleChange = (e: React.SyntheticEvent) => { - const { value } = e.currentTarget; + handleChange = (value: string) => { this.props.dispatch({ type: Actions.SEARCH_QUERY_CHANGE, payload: value }); this.debouncedOFSearch(value); } @@ -67,18 +67,14 @@ export class RawCropCatalog extends React.Component { title={t("Choose a crop")} backTo={"/app/designer/plants"} /> -
- - {this.showResultChangeSpinner && - } -
+ : undefined} />
diff --git a/frontend/farm_designer/plants/plant_inventory.tsx b/frontend/farm_designer/plants/plant_inventory.tsx index a37397abf..214e7c9ff 100644 --- a/frontend/farm_designer/plants/plant_inventory.tsx +++ b/frontend/farm_designer/plants/plant_inventory.tsx @@ -13,6 +13,7 @@ import { DesignerPanel, DesignerPanelContent, DesignerPanelTop, } from "../designer_panel"; import { t } from "../../i18next_wrapper"; +import { SearchField } from "../../ui/search_field"; export interface PlantInventoryProps { plants: TaggedPlant[]; @@ -34,12 +35,8 @@ export function mapStateToProps(props: Everything): PlantInventoryProps { } export class RawPlants extends React.Component { - state: State = { searchTerm: "" }; - update = ({ currentTarget }: React.SyntheticEvent) => - this.setState({ searchTerm: currentTarget.value }) - render() { return @@ -47,8 +44,9 @@ export class RawPlants extends React.Component { panel={Panel.Plants} linkTo={"/app/designer/plants/crop_search"} title={t("Add plant")}> - + this.setState({ searchTerm })} /> ", () => { const fakeProps = (): GroupListPanelProps => { @@ -55,8 +56,7 @@ describe("", () => { it("changes search term", () => { const p = fakeProps(); const wrapper = shallow(); - wrapper.find("input").first().simulate("change", - { currentTarget: { value: "one" } }); + wrapper.find(SearchField).simulate("change", "one"); expect(wrapper.state().searchTerm).toEqual("one"); }); diff --git a/frontend/farm_designer/point_groups/criteria/__tests__/component_test.tsx b/frontend/farm_designer/point_groups/criteria/__tests__/component_test.tsx index dee0acf3f..2641a2b70 100644 --- a/frontend/farm_designer/point_groups/criteria/__tests__/component_test.tsx +++ b/frontend/farm_designer/point_groups/criteria/__tests__/component_test.tsx @@ -95,7 +95,7 @@ describe("", () => { p.pointsSelectedByGroup = [point1, point2, point3]; p.group.body.point_ids = [1]; const wrapper = mount(); - ["1manually selected", "2selected by filters"].map(string => + ["1 manually selected", "2 selected by filters"].map(string => expect(wrapper.text()).toContain(string)); }); @@ -108,7 +108,7 @@ describe("", () => { p.pointsSelectedByGroup = [point1, point2]; p.group.body.point_ids = []; const wrapper = mount(); - ["0manually selected", "2selected by filters"].map(string => + ["0 manually selected", "2 selected by filters"].map(string => expect(wrapper.text()).toContain(string)); }); diff --git a/frontend/farm_designer/point_groups/criteria/component.tsx b/frontend/farm_designer/point_groups/criteria/component.tsx index acbfed7b7..c93bb5edd 100644 --- a/frontend/farm_designer/point_groups/criteria/component.tsx +++ b/frontend/farm_designer/point_groups/criteria/component.tsx @@ -153,10 +153,7 @@ export const GroupPointCountBreakdown = dispatch={props.dispatch} />; return
-
- {manualPoints.length} -
-

{t("manually selected")}

+

{`${manualPoints.length} ${t("manually selected")}`}

{props.iconDisplay && manualPoints.length > 0 && @@ -166,10 +163,7 @@ export const GroupPointCountBreakdown = {props.shouldDisplay(Feature.criteria_groups) &&
-
- {criteriaPoints.length} -
-

{t("selected by filters")}

+

{`${criteriaPoints.length} ${t("selected by filters")}`}

{props.iconDisplay && criteriaPoints.length > 0 && @@ -186,7 +180,7 @@ export const PointTypeSelection = (props: PointTypeSelectionProps) =>

{t("Select all")}

{
} - "]]} selectedItem={noDayCriteria diff --git a/frontend/farm_designer/point_groups/group_list_panel.tsx b/frontend/farm_designer/point_groups/group_list_panel.tsx index ce79dea79..86aee4c42 100644 --- a/frontend/farm_designer/point_groups/group_list_panel.tsx +++ b/frontend/farm_designer/point_groups/group_list_panel.tsx @@ -16,6 +16,7 @@ import { import { Content } from "../../constants"; import { selectAllActivePoints } from "../../resources/selectors"; import { createGroup } from "./actions"; +import { SearchField } from "../../ui/search_field"; export interface GroupListPanelProps { dispatch: Function; @@ -39,10 +40,6 @@ export class RawGroupListPanel extends React.Component { state: State = { searchTerm: "" }; - update = ({ currentTarget }: React.SyntheticEvent) => { - this.setState({ searchTerm: currentTarget.value }); - } - navigate = (id: number) => history.push(`/app/designer/groups/${id}`); render() { @@ -52,10 +49,9 @@ export class RawGroupListPanel panel={Panel.Groups} onClick={() => this.props.dispatch(createGroup({ pointUuids: [] }))} title={t("Add group")}> - + this.setState({ searchTerm })} /> />", () => { const fakeProps = (): PointsProps => ({ @@ -48,8 +49,7 @@ describe(" />", () => { p.genericPoints[0].body.name = "point 0"; p.genericPoints[1].body.name = "point 1"; const wrapper = shallow(); - wrapper.find("input").first().simulate("change", - { currentTarget: { value: "0" } }); + wrapper.find(SearchField).simulate("change", "0"); expect(wrapper.state().searchTerm).toEqual("0"); }); diff --git a/frontend/farm_designer/points/point_info.tsx b/frontend/farm_designer/points/point_info.tsx index f71ec2b99..1a8b0a727 100644 --- a/frontend/farm_designer/points/point_info.tsx +++ b/frontend/farm_designer/points/point_info.tsx @@ -56,6 +56,7 @@ export class RawEditPoint extends React.Component { switch (key) { case "color": case "created_by": + case "removal_method": case "type": return
; diff --git a/frontend/farm_designer/points/point_inventory.tsx b/frontend/farm_designer/points/point_inventory.tsx index 4412413a8..524eb75ad 100644 --- a/frontend/farm_designer/points/point_inventory.tsx +++ b/frontend/farm_designer/points/point_inventory.tsx @@ -13,6 +13,7 @@ import { import { selectAllGenericPointers } from "../../resources/selectors"; import { TaggedGenericPointer } from "farmbot"; import { t } from "../../i18next_wrapper"; +import { SearchField } from "../../ui/search_field"; export interface PointsProps { genericPoints: TaggedGenericPointer[]; @@ -37,10 +38,6 @@ export function mapStateToProps(props: Everything): PointsProps { export class RawPoints extends React.Component { state: PointsState = { searchTerm: "" }; - update = ({ currentTarget }: React.SyntheticEvent) => { - this.setState({ searchTerm: currentTarget.value }); - } - render() { return @@ -48,8 +45,9 @@ export class RawPoints extends React.Component { panel={Panel.Points} linkTo={"/app/designer/points/add"} title={t("Add point")}> - + this.setState({ searchTerm })} /> ({ edit: jest.fn() })); import * as React from "react"; import { mount, shallow } from "enzyme"; import { - RawSavedGardens as SavedGardens, mapStateToProps, - SavedGardenHUD, savedGardenOpen, + RawSavedGardens as SavedGardens, mapStateToProps, SavedGardenHUD, } from "../saved_gardens"; import { clickButton } from "../../../__test_support__/helpers"; import { @@ -31,6 +30,7 @@ import { import { SavedGardensProps } from "../interfaces"; import { closeSavedGarden } from "../actions"; import { Actions } from "../../../constants"; +import { SearchField } from "../../../ui/search_field"; describe("", () => { const fakeProps = (): SavedGardensProps => ({ @@ -60,8 +60,7 @@ describe("", () => { it("changes search term", () => { const wrapper = shallow(); expect(wrapper.state().searchTerm).toEqual(""); - wrapper.find("input").first().simulate("change", - { currentTarget: { value: "spring" } }); + wrapper.find(SearchField).simulate("change", "spring"); expect(wrapper.state().searchTerm).toEqual("spring"); }); @@ -99,13 +98,6 @@ describe("mapStateToProps()", () => { }); }); -describe("savedGardenOpen", () => { - it("is open", () => { - const result = savedGardenOpen(["", "", "", "gardens", "4", ""]); - expect(result).toEqual(4); - }); -}); - describe("", () => { it("renders", () => { const wrapper = mount(); diff --git a/frontend/farm_designer/saved_gardens/saved_gardens.tsx b/frontend/farm_designer/saved_gardens/saved_gardens.tsx index b33296208..217578275 100644 --- a/frontend/farm_designer/saved_gardens/saved_gardens.tsx +++ b/frontend/farm_designer/saved_gardens/saved_gardens.tsx @@ -18,6 +18,7 @@ import { EmptyStateWrapper, EmptyStateGraphic, } from "../../ui/empty_state_wrapper"; import { Content } from "../../constants"; +import { SearchField } from "../../ui/search_field"; export const mapStateToProps = (props: Everything): SavedGardensProps => ({ savedGardens: selectAllSavedGardens(props.resources.index), @@ -35,9 +36,6 @@ export class RawSavedGardens unselectPlant(this.props.dispatch)(); } - onChange = (e: React.SyntheticEvent) => - this.setState({ searchTerm: e.currentTarget.value }); - render() { return @@ -46,8 +44,9 @@ export class RawSavedGardens panel={Panel.SavedGardens} linkTo={"/app/designer/gardens/add"} title={t("Add garden")}> - + this.setState({ searchTerm })} /> 0} @@ -62,11 +61,6 @@ export class RawSavedGardens } } -/** Check if a SavedGarden is currently open (URL approach). */ -export const savedGardenOpen = (pathArray: string[]) => - pathArray[3] === "gardens" && parseInt(pathArray[4]) > 0 - ? parseInt(pathArray[4]) : false; - /** Sticky an indicator and actions menu when a SavedGarden is open. */ export const SavedGardenHUD = (props: { dispatch: Function }) =>
diff --git a/frontend/farm_designer/tools/__tests__/index_test.tsx b/frontend/farm_designer/tools/__tests__/index_test.tsx index 66a735318..c9b524f47 100644 --- a/frontend/farm_designer/tools/__tests__/index_test.tsx +++ b/frontend/farm_designer/tools/__tests__/index_test.tsx @@ -34,6 +34,7 @@ import { edit, save } from "../../../api/crud"; import { ToolSelection } from "../tool_slot_edit_components"; import { ToolsProps } from "../interfaces"; import { mapPointClickAction } from "../../map/actions"; +import { SearchField } from "../../../ui/search_field"; describe("", () => { const fakeProps = (): ToolsProps => ({ @@ -117,8 +118,7 @@ describe("", () => { p.tools[0].body.name = "tool 0"; p.tools[1].body.name = "tool 1"; const wrapper = shallow(); - wrapper.find("input").first().simulate("change", - { currentTarget: { value: "0" } }); + wrapper.find(SearchField).simulate("change", "0"); expect(wrapper.state().searchTerm).toEqual("0"); }); diff --git a/frontend/farm_designer/tools/index.tsx b/frontend/farm_designer/tools/index.tsx index 36648d70c..c3b061378 100644 --- a/frontend/farm_designer/tools/index.tsx +++ b/frontend/farm_designer/tools/index.tsx @@ -29,6 +29,7 @@ import { BotOriginQuadrant } from "../interfaces"; import { mapPointClickAction } from "../map/actions"; import { getMode } from "../map/util"; import { Mode } from "../map/interfaces"; +import { SearchField } from "../../ui/search_field"; const toolStatus = (value: number | undefined): string => { switch (value) { @@ -41,10 +42,6 @@ const toolStatus = (value: number | undefined): string => { export class RawTools extends React.Component { state: ToolsState = { searchTerm: "" }; - update = ({ currentTarget }: React.SyntheticEvent) => { - this.setState({ searchTerm: currentTarget.value }); - } - getToolName = (toolId: number | undefined): string | undefined => { const foundTool = this.props.tools.filter(tool => tool.body.id === toolId)[0]; return foundTool ? foundTool.body.name : undefined; @@ -183,8 +180,9 @@ export class RawTools extends React.Component { panel={Panel.Tools} linkTo={!hasTools ? "/app/designer/tools/add" : undefined} title={!hasTools ? this.strings.titleText : undefined}> - + this.setState({ searchTerm })} /> />", () => { const fakeProps = (): WeedsProps => ({ @@ -20,8 +21,7 @@ describe(" />", () => { it("changes search term", () => { const wrapper = shallow(); - wrapper.find("input").first().simulate("change", - { currentTarget: { value: "0" } }); + wrapper.find(SearchField).simulate("change", "0"); expect(wrapper.state().searchTerm).toEqual("0"); }); diff --git a/frontend/farm_designer/weeds/weeds_inventory.tsx b/frontend/farm_designer/weeds/weeds_inventory.tsx index 50b3d1c0d..cfd94068f 100644 --- a/frontend/farm_designer/weeds/weeds_inventory.tsx +++ b/frontend/farm_designer/weeds/weeds_inventory.tsx @@ -13,6 +13,7 @@ import { t } from "../../i18next_wrapper"; import { TaggedWeedPointer } from "farmbot"; import { selectAllWeedPointers } from "../../resources/selectors"; import { WeedInventoryItem } from "./weed_inventory_item"; +import { SearchField } from "../../ui/search_field"; export interface WeedsProps { weeds: TaggedWeedPointer[]; @@ -33,10 +34,6 @@ export const mapStateToProps = (props: Everything): WeedsProps => ({ export class RawWeeds extends React.Component { state: WeedsState = { searchTerm: "" }; - update = ({ currentTarget }: React.SyntheticEvent) => { - this.setState({ searchTerm: currentTarget.value }); - } - render() { return @@ -44,8 +41,9 @@ export class RawWeeds extends React.Component { panel={Panel.Weeds} linkTo={"/app/designer/weeds/add"} title={t("Add weed")}> - + this.setState({ searchTerm })} /> />", () => { const fakeProps = (): ZonesProps => ({ @@ -30,8 +31,7 @@ describe(" />", () => { it("changes search term", () => { const wrapper = shallow(); - wrapper.find("input").first().simulate("change", - { currentTarget: { value: "0" } }); + wrapper.find(SearchField).simulate("change", "0"); expect(wrapper.state().searchTerm).toEqual("0"); }); diff --git a/frontend/farm_designer/zones/zones_inventory.tsx b/frontend/farm_designer/zones/zones_inventory.tsx index 54d049e67..c3d8e7b61 100644 --- a/frontend/farm_designer/zones/zones_inventory.tsx +++ b/frontend/farm_designer/zones/zones_inventory.tsx @@ -17,6 +17,7 @@ import { import { GroupInventoryItem } from "../point_groups/group_inventory_item"; import { history } from "../../history"; import { initSaveGetId } from "../../api/crud"; +import { SearchField } from "../../ui/search_field"; export interface ZonesProps { dispatch: Function; @@ -37,10 +38,6 @@ export const mapStateToProps = (props: Everything): ZonesProps => ({ export class RawZones extends React.Component { state: ZonesState = { searchTerm: "" }; - update = ({ currentTarget }: React.SyntheticEvent) => { - this.setState({ searchTerm: currentTarget.value }); - } - navigate = (id: number) => history.push(`/app/designer/zones/${id}`); render() { @@ -53,8 +50,9 @@ export class RawZones extends React.Component { })) .then((id: number) => this.navigate(id)).catch(() => { })} title={t("Add zone")}> - + this.setState({ searchTerm })} /> ({ kind: "initial", @@ -540,9 +541,7 @@ describe("", () => { it("changes search term", () => { const p = fakeProps(); const wrapper = shallow(); - wrapper.find("input").simulate("change", { - currentTarget: { value: "new" } - }); + wrapper.find(SearchField).simulate("change", "new"); expect(updateSearchTerm).toHaveBeenCalledWith("new"); }); diff --git a/frontend/folders/component.tsx b/frontend/folders/component.tsx index a5e3ec9cc..1111ac3c2 100644 --- a/frontend/folders/component.tsx +++ b/frontend/folders/component.tsx @@ -46,6 +46,7 @@ import { Content } from "../constants"; import { StepDragger, NULL_DRAGGER_ID } from "../draggable/step_dragger"; import { variableList } from "../sequences/locals_list/variable_support"; import { UUID } from "../resources/interfaces"; +import { SearchField } from "../ui/search_field"; export const FolderListItem = (props: FolderItemProps) => { const { sequence, movedSequenceUuid } = props; @@ -328,16 +329,10 @@ export class Folders extends React.Component { export const FolderPanelTop = (props: FolderPanelTopProps) =>
-
-
- - updateSearchTerm(e.currentTarget.value)} - type="text" name="searchTerm" - placeholder={t("Search sequences...")} /> -
-
+ diff --git a/frontend/logs/__tests__/index_test.tsx b/frontend/logs/__tests__/index_test.tsx index 66c4d5498..ac5f67230 100644 --- a/frontend/logs/__tests__/index_test.tsx +++ b/frontend/logs/__tests__/index_test.tsx @@ -10,6 +10,7 @@ import { fakeLog } from "../../__test_support__/fake_state/resources"; import { LogsProps } from "../interfaces"; import { MessageType } from "../../sequences/interfaces"; import { fakeTimeSettings } from "../../__test_support__/fake_time_settings"; +import { SearchField } from "../../ui/search_field"; describe("", () => { function fakeLogs(): TaggedLog[] { @@ -176,8 +177,7 @@ describe("", () => { it("changes search term", () => { const p = fakeProps(); const wrapper = shallow(); - wrapper.find("input").first().simulate("change", - { currentTarget: { value: "one" } }); + wrapper.find(SearchField).first().simulate("change", "one"); expect(wrapper.state().searchTerm).toEqual("one"); }); }); diff --git a/frontend/logs/index.tsx b/frontend/logs/index.tsx index 2cbd8e013..506c8a16f 100644 --- a/frontend/logs/index.tsx +++ b/frontend/logs/index.tsx @@ -17,6 +17,7 @@ import { NumberConfigKey } from "farmbot/dist/resources/configs/web_app"; import { t } from "../i18next_wrapper"; import { TimeSettings } from "../interfaces"; import { timeFormatString } from "../util"; +import { SearchField } from "../ui/search_field"; /** Format log date and time for display in the app. */ export const formatLogTime = @@ -125,15 +126,10 @@ export class RawLogs extends React.Component> { -
-
- - - this.setState({ searchTerm: e.currentTarget.value })} - placeholder={t("Search logs...")} /> -
-
+ this.setState({ searchTerm })} />
diff --git a/frontend/regimens/list/__tests__/index_test.tsx b/frontend/regimens/list/__tests__/index_test.tsx index c1d84c773..c827b9c8f 100644 --- a/frontend/regimens/list/__tests__/index_test.tsx +++ b/frontend/regimens/list/__tests__/index_test.tsx @@ -1,9 +1,8 @@ import * as React from "react"; -import { mount } from "enzyme"; +import { mount, shallow } from "enzyme"; import { RegimensList } from "../index"; import { RegimensListProps } from "../../interfaces"; import { fakeRegimen } from "../../../__test_support__/fake_state/resources"; -import { inputEvent } from "../../../__test_support__/fake_html_events"; describe("", () => { function fakeProps(): RegimensListProps { @@ -25,8 +24,8 @@ describe("", () => { }); it("sets search term", () => { - const wrapper = mount(); - wrapper.instance().onChange(inputEvent("term")); + const wrapper = shallow(); + wrapper.find("RegimenListHeader").simulate("change", "term"); expect(wrapper.state().searchTerm).toEqual("term"); }); }); diff --git a/frontend/regimens/list/index.tsx b/frontend/regimens/list/index.tsx index 64c20998b..677bed5e3 100644 --- a/frontend/regimens/list/index.tsx +++ b/frontend/regimens/list/index.tsx @@ -7,23 +7,21 @@ import { sortResourcesById } from "../../util"; import { t } from "../../i18next_wrapper"; import { EmptyStateWrapper, EmptyStateGraphic } from "../../ui/empty_state_wrapper"; import { Content } from "../../constants"; +import { SearchField } from "../../ui/search_field"; interface RegimenListHeaderProps { - onChange(e: React.SyntheticEvent): void; + searchTerm: string; + onChange(searchTerm: string): void; regimenCount: number; dispatch: Function; } const RegimenListHeader = (props: RegimenListHeaderProps) =>
-
-
- - -
-
+
; @@ -55,16 +53,13 @@ export class RegimensList extends ; } - onChange = (e: React.SyntheticEvent) => { - this.setState({ searchTerm: e.currentTarget.value }); - } - render() { return
+ searchTerm={this.state.searchTerm} + onChange={searchTerm => this.setState({ searchTerm })} /> 0} diff --git a/frontend/ui/__tests__/search_field_test.tsx b/frontend/ui/__tests__/search_field_test.tsx new file mode 100644 index 000000000..aeb70072a --- /dev/null +++ b/frontend/ui/__tests__/search_field_test.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { mount, shallow } from "enzyme"; +import { SearchField, SearchFieldProps } from "../search_field"; +import { changeEvent } from "../../__test_support__/fake_html_events"; + +describe("", () => { + const fakeProps = (): SearchFieldProps => ({ + onChange: jest.fn(), + searchTerm: "", + placeholder: "search...", + }); + + it("renders", () => { + const wrapper = mount(); + expect(wrapper.find("input").props().placeholder).toEqual("search..."); + }); + + it("changes search term", () => { + const p = fakeProps(); + const wrapper = shallow(); + const e = changeEvent("new"); + wrapper.find("input").simulate("change", e); + expect(p.onChange).toHaveBeenCalledWith("new"); + }); + + it("changes search term on key press", () => { + const p = fakeProps(); + p.onKeyPress = jest.fn(); + const wrapper = shallow(); + const e = changeEvent("new"); + wrapper.find("input").simulate("KeyPress", e); + expect(p.onKeyPress).toHaveBeenCalledWith("new"); + }); + + it("doesn't change search term on key press", () => { + const p = fakeProps(); + p.onKeyPress = undefined; + const wrapper = shallow(); + const e = changeEvent("new"); + wrapper.find("input").simulate("KeyPress", e); + expect(p.onChange).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/ui/search_field.tsx b/frontend/ui/search_field.tsx new file mode 100644 index 000000000..b87ce9342 --- /dev/null +++ b/frontend/ui/search_field.tsx @@ -0,0 +1,30 @@ +import * as React from "react"; +import { ErrorBoundary } from "../error_boundary"; + +export interface SearchFieldProps { + onChange(searchTerm: string): void; + onKeyPress?: (searchTerm: string) => void; + searchTerm: string; + placeholder: string; + customLeftIcon?: React.ReactElement; + customRightIcon?: React.ReactElement; + autoFocus?: boolean; +} + +export const SearchField = (props: SearchFieldProps) => +
+
+
+ + {props.customLeftIcon || } + props.onChange(e.currentTarget.value)} + onKeyPress={e => props.onKeyPress?.(e.currentTarget.value)} + placeholder={props.placeholder} /> + {props.searchTerm && props.customRightIcon} + +
+
+
;