misc fixes and updates

pull/1219/head
gabrielburnworth 2019-06-04 15:07:24 -07:00
parent 6f4e6b7af6
commit feb7b07a6f
19 changed files with 252 additions and 127 deletions

View File

@ -4,7 +4,7 @@ describe("fetchLabFeatures", () => {
Object.defineProperty(window.location, "reload", { value: jest.fn() }); Object.defineProperty(window.location, "reload", { value: jest.fn() });
it("basically just initializes stuff", () => { it("basically just initializes stuff", () => {
const val = fetchLabFeatures(jest.fn()); const val = fetchLabFeatures(jest.fn());
expect(val.length).toBe(10); expect(val.length).toBe(9);
expect(val[0].value).toBeFalsy(); expect(val[0].value).toBeFalsy();
const { callback } = val[0]; const { callback } = val[0];
if (callback) { if (callback) {

View File

@ -38,18 +38,6 @@ export const fetchLabFeatures =
storageKey: BooleanSetting.hide_webcam_widget, storageKey: BooleanSetting.hide_webcam_widget,
value: false value: false
}, },
{
name: t("Dynamic map size"),
description: t(Content.DYNAMIC_MAP_SIZE),
storageKey: BooleanSetting.dynamic_map,
value: false
},
{
name: t("Double default map dimensions"),
description: t(Content.DOUBLE_MAP_DIMENSIONS),
storageKey: BooleanSetting.map_xl,
value: false
},
{ {
name: t("Display plant animations"), name: t("Display plant animations"),
description: t(Content.PLANT_ANIMATIONS), description: t(Content.PLANT_ANIMATIONS),
@ -91,6 +79,12 @@ export const fetchLabFeatures =
value: false, value: false,
displayInvert: true, displayInvert: true,
}, },
{
name: t("Dynamic map size"),
description: t(Content.DYNAMIC_MAP_SIZE),
storageKey: BooleanSetting.dynamic_map,
value: false
},
].map(fetchSettingValue(getConfigValue))); ].map(fetchSettingValue(getConfigValue)));
/** Always allow toggling from true => false (deactivate). /** Always allow toggling from true => false (deactivate).

View File

@ -72,7 +72,7 @@ describe("<Move />", () => {
it("changes step size", () => { it("changes step size", () => {
const p = fakeProps(); const p = fakeProps();
const wrapper = mount(<Move {...p} />); const wrapper = mount(<Move {...p} />);
clickButton(wrapper, 1, "1"); clickButton(wrapper, 0, "1");
expect(p.dispatch).toHaveBeenCalledWith({ expect(p.dispatch).toHaveBeenCalledWith({
type: Actions.CHANGE_STEP_SIZE, type: Actions.CHANGE_STEP_SIZE,
payload: 1 payload: 1

View File

@ -1,7 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Widget, WidgetBody, WidgetHeader } from "../../ui"; import { Widget, WidgetBody, WidgetHeader } from "../../ui";
import { EStopButton } from "../../devices/components/e_stop_btn";
import { MustBeOnline } from "../../devices/must_be_online"; import { MustBeOnline } from "../../devices/must_be_online";
import { validBotLocationData } from "../../util"; import { validBotLocationData } from "../../util";
import { toggleWebAppBool } from "../../config_storage/actions"; import { toggleWebAppBool } from "../../config_storage/actions";
@ -37,10 +35,6 @@ export class Move extends React.Component<MoveProps, {}> {
toggle={this.toggle} toggle={this.toggle}
getValue={this.getValue} /> getValue={this.getValue} />
</Popover> </Popover>
<EStopButton
bot={this.props.bot}
forceUnlock={this.getValue(
BooleanSetting.disable_emergency_unlock_confirmation)} />
</WidgetHeader> </WidgetHeader>
<WidgetBody> <WidgetBody>
<MustBeOnline <MustBeOnline

View File

@ -1,9 +1,11 @@
import * as React from "react"; import * as React from "react";
import { mount } from "enzyme"; import { mount, shallow } from "enzyme";
import { PeripheralForm } from "../peripheral_form"; import { PeripheralForm } from "../peripheral_form";
import { TaggedPeripheral, SpecialStatus } from "farmbot"; import { TaggedPeripheral, SpecialStatus } from "farmbot";
import { PeripheralFormProps } from "../interfaces";
import { Actions } from "../../../constants";
describe("<PeripheralForm/>", function () { describe("<PeripheralForm/>", () => {
const dispatch = jest.fn(); const dispatch = jest.fn();
const peripherals: TaggedPeripheral[] = [ const peripherals: TaggedPeripheral[] = [
{ {
@ -27,22 +29,45 @@ describe("<PeripheralForm/>", function () {
} }
}, },
]; ];
const fakeProps = (): PeripheralFormProps => ({ dispatch, peripherals });
it("renders a list of editable peripherals, in sorted order", function () { const expectedPayload = (update: Object) =>
expect.objectContaining({
payload: expect.objectContaining({
update
}),
type: Actions.EDIT_RESOURCE
});
it("renders a list of editable peripherals, in sorted order", () => {
const form = mount(<PeripheralForm dispatch={dispatch} const form = mount(<PeripheralForm dispatch={dispatch}
peripherals={peripherals} />); peripherals={peripherals} />);
const inputs = form.find("input"); const inputs = form.find("input");
const buttons = form.find("button");
expect(inputs.at(0).props().value).toEqual("GPIO 2"); expect(inputs.at(0).props().value).toEqual("GPIO 2");
inputs.at(0).simulate("change"); expect(inputs.at(1).props().value).toEqual("GPIO 13 - LED");
expect(inputs.at(1).props().value).toEqual("2"); });
inputs.at(1).simulate("change");
it("updates label", () => {
const p = fakeProps();
const form = shallow(<PeripheralForm {...p} />);
const inputs = form.find("input");
inputs.at(0).simulate("change", { currentTarget: { value: "GPIO 3" } });
expect(p.dispatch).toHaveBeenCalledWith(
expectedPayload({ label: "GPIO 3" }));
});
it("updates pin", () => {
const p = fakeProps();
const form = shallow(<PeripheralForm {...p} />);
form.find("FBSelect").at(0).simulate("change", { value: 3 });
expect(p.dispatch).toHaveBeenCalledWith(expectedPayload({ pin: 3 }));
});
it("deletes peripheral", () => {
const p = fakeProps();
const form = shallow(<PeripheralForm {...p} />);
const buttons = form.find("button");
buttons.at(0).simulate("click"); buttons.at(0).simulate("click");
expect(inputs.at(2).props().value).toEqual("GPIO 13 - LED"); expect(p.dispatch).toHaveBeenCalledWith(expect.any(Function));
inputs.at(2).simulate("change");
expect(inputs.at(3).props().value).toEqual("13");
inputs.at(3).simulate("change");
buttons.at(1).simulate("click");
expect(dispatch).toHaveBeenCalledTimes(6);
}); });
}); });

View File

@ -1,35 +1,44 @@
import * as React from "react"; import * as React from "react";
import { destroy, edit } from "../../api/crud"; import { destroy, edit } from "../../api/crud";
import { PeripheralFormProps } from "./interfaces"; import { PeripheralFormProps } from "./interfaces";
import { sortResourcesById } from "../../util"; import { sortResourcesById } from "../../util";
import { KeyValEditRow } from "../key_val_edit_row";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";
import { Row, Col, FBSelect } from "../../ui";
import {
pinDropdowns
} from "../../sequences/step_tiles/pin_and_peripheral_support";
export function PeripheralForm(props: PeripheralFormProps) { export function PeripheralForm(props: PeripheralFormProps) {
const { dispatch, peripherals } = props; const { dispatch, peripherals } = props;
return <div> return <div>
{sortResourcesById(peripherals).map(p => { {sortResourcesById(peripherals).map(p => {
return <Row key={p.uuid + p.body.id}>
return <KeyValEditRow <Col xs={6}>
key={p.uuid} <input type="text"
label={p.body.label} placeholder={t("Name")}
onLabelChange={(e) => { value={p.body.label}
const { value } = e.currentTarget; onChange={e =>
dispatch(edit(p, { label: value })); dispatch(edit(p, { label: e.currentTarget.value }))} />
}} </Col>
labelPlaceholder="Name" <Col xs={4}>
value={(p.body.pin || "").toString()} <FBSelect
valuePlaceholder={t("Pin #")} selectedItem={{
onValueChange={(e) => { label: t("Pin ") + `${p.body.pin}`,
const { value } = e.currentTarget; value: p.body.pin || ""
const update: Partial<typeof p.body> = { pin: parseInt(value, 10) }; }}
dispatch(edit(p, update)); onChange={d =>
}} dispatch(edit(p, { pin: parseInt(d.value.toString(), 10) }))}
onClick={() => { dispatch(destroy(p.uuid)); }} list={pinDropdowns(n => n)} />
disabled={false} </Col>
valueType="number" />; <Col xs={2}>
<button
className="red fb-button"
onClick={() => dispatch(destroy(p.uuid))}>
<i className="fa fa-minus" />
</button>
</Col>
</Row>;
})} })}
</div>; </div>;
} }

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { error } from "farmbot-toastr"; import { error } from "farmbot-toastr";
import { SensorList } from "./sensor_list"; import { SensorList } from "./sensor_list";
import { SensorForm } from "./sensor_form"; import { SensorForm } from "./sensor_form";
@ -85,7 +84,7 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
className="fb-button green" className="fb-button green"
type="button" type="button"
onClick={this.stockSensors}> onClick={this.stockSensors}>
<i className="fa fa-plus" /> <i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
{t("Stock sensors")} {t("Stock sensors")}
</button> </button>
</WidgetHeader> </WidgetHeader>

View File

@ -286,6 +286,9 @@
.save-btn { .save-btn {
margin: 1rem; margin: 1rem;
} }
.location-form {
width: 100% !important;
}
} }
.add-farm-event-panel button.red, .add-farm-event-panel button.red,

View File

@ -280,7 +280,8 @@ a {
.drag-drop-area { .drag-drop-area {
&.visible { &.visible {
margin: 0.75rem 0; margin: 0.75rem 0;
margin-right: 15px; margin-right: 25px;
margin-left: 10px;
border-style: dashed; border-style: dashed;
border-width: 2px; border-width: 2px;
border-color: $light_gray; border-color: $light_gray;
@ -984,6 +985,10 @@ ul {
} }
} }
.map-size-inputs {
margin-top: 1rem;
}
.release-notes-button { .release-notes-button {
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
@ -1048,9 +1053,6 @@ ul {
p { p {
margin-top: 0.75rem; margin-top: 0.75rem;
} }
.fa-plus {
margin-right: 0.5rem;
}
.sensor-reading-display { .sensor-reading-display {
&.moisture-sensor { &.moisture-sensor {
background: linear-gradient(to right, rgba($blue, 0) 20%, $blue 80%, rgba($blue, 0) 85%); background: linear-gradient(to right, rgba($blue, 0) 20%, $blue 80%, rgba($blue, 0) 85%);

View File

@ -70,6 +70,12 @@
} }
} }
.sequence-editor-content {
hr {
margin-right: 15px;
}
}
.sequence-editor-tools, .sequence-editor-tools,
.regimen-editor-tools { .regimen-editor-tools {
margin-right: 15px; margin-right: 15px;
@ -129,7 +135,8 @@
} }
.sequence-steps { .sequence-steps {
margin-right: 15px; margin-right: 25px;
margin-left: 10px;
} }
.step-button-cluster, .step-button-cluster,

View File

@ -6,36 +6,22 @@ describe("getDefaultAxisLength()", () => {
const axes = getDefaultAxisLength(() => false); const axes = getDefaultAxisLength(() => false);
expect(axes).toEqual({ x: 2900, y: 1400 }); expect(axes).toEqual({ x: 2900, y: 1400 });
}); });
it("returns XL axis lengths", () => {
const axes = getDefaultAxisLength(() => true);
expect(axes).toEqual({ x: 5900, y: 2900 });
});
}); });
describe("getGridSize()", () => { describe("getGridSize()", () => {
it("returns default grid size", () => { it("returns default grid size", () => {
const grid = getGridSize( const grid = getGridSize(
k => ({ dynamic_map: false, map_xl: false } as WebAppConfig)[k], { k => ({ dynamic_map: false } as WebAppConfig)[k], {
x: { value: 100, isDefault: false }, x: { value: 100, isDefault: false },
y: { value: 200, isDefault: false } y: { value: 200, isDefault: false }
}); });
expect(grid).toEqual({ x: 2900, y: 1400 }); expect(grid).toEqual({ x: 2900, y: 1400 });
}); });
it("returns XL grid size", () => {
const grid = getGridSize(
k => ({ dynamic_map: false, map_xl: true } as WebAppConfig)[k], {
x: { value: 100, isDefault: false },
y: { value: 200, isDefault: false }
});
expect(grid).toEqual({ x: 5900, y: 2900 });
});
it("returns custom grid size", () => { it("returns custom grid size", () => {
const grid = getGridSize( const grid = getGridSize(
k => ({ k => ({
dynamic_map: false, map_xl: true, map_size_x: 300, map_size_y: 400 dynamic_map: false, map_size_x: 300, map_size_y: 400
} as WebAppConfig)[k], { } as WebAppConfig)[k], {
x: { value: 100, isDefault: false }, x: { value: 100, isDefault: false },
y: { value: 200, isDefault: false } y: { value: 200, isDefault: false }
@ -45,7 +31,7 @@ describe("getGridSize()", () => {
it("returns grid size using bot size", () => { it("returns grid size using bot size", () => {
const grid = getGridSize( const grid = getGridSize(
k => ({ dynamic_map: true, map_xl: false } as WebAppConfig)[k], { k => ({ dynamic_map: true } as WebAppConfig)[k], {
x: { value: 100, isDefault: false }, x: { value: 100, isDefault: false },
y: { value: 200, isDefault: false } y: { value: 200, isDefault: false }
}); });

View File

@ -26,11 +26,7 @@ export const getDefaultAxisLength =
if (isFinite(mapSizeX) && isFinite(mapSizeY)) { if (isFinite(mapSizeX) && isFinite(mapSizeY)) {
return { x: mapSizeX, y: mapSizeY }; return { x: mapSizeX, y: mapSizeY };
} }
if (getConfigValue(BooleanSetting.map_xl)) { return { x: 2900, y: 1400 };
return { x: 5900, y: 2900 };
} else {
return { x: 2900, y: 1400 };
}
}; };
export const getGridSize = export const getGridSize =

View File

@ -147,7 +147,7 @@ export function PlantPanel(props: PlantPanelProps) {
const { const {
info, onDestroy, updatePlant, dispatch, inSavedGarden, timeSettings info, onDestroy, updatePlant, dispatch, inSavedGarden, timeSettings
} = props; } = props;
const { name, slug, plantedAt, daysOld, uuid, plantStatus } = info; const { slug, plantedAt, daysOld, uuid, plantStatus } = info;
let { x, y } = info; let { x, y } = info;
const isEditing = !!onDestroy; const isEditing = !!onDestroy;
if (isEditing) { x = round(x); y = round(y); } if (isEditing) { x = round(x); y = round(y); }
@ -157,9 +157,6 @@ export function PlantPanel(props: PlantPanelProps) {
{t("Plant Info")} {t("Plant Info")}
</label> </label>
<ul> <ul>
<ListItem name={t("Full Name")}>
{startCase(name)}
</ListItem>
<ListItem name={t("Plant Type")}> <ListItem name={t("Plant Type")}>
<Link <Link
title={t("View crop info")} title={t("View crop info")}

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { AccountMenuProps } from "./interfaces"; import { AccountMenuProps } from "./interfaces";
import { docLink } from "../ui/doc_link"; import { docLink } from "../ui/doc_link";
import { Link } from "../link"; import { Link } from "../link";
@ -21,11 +20,10 @@ export const AdditionalMenu = (props: AccountMenuProps) => {
{t("Logs")} {t("Logs")}
</Link> </Link>
</div> </div>
{DevSettings.futureFeaturesEnabled() && <Link to="/app/help" onClick={props.close("accountMenuOpen")}>
<Link to="/app/help" onClick={props.close("accountMenuOpen")}> <i className="fa fa-question-circle"></i>
<i className="fa fa-question-circle"></i> {t("Help")}
{t("Help")} </Link>
</Link>}
{!DevSettings.futureFeaturesEnabled() && {!DevSettings.futureFeaturesEnabled() &&
<div> <div>
<a href={docLink("the-farmbot-web-app")} <a href={docLink("the-farmbot-web-app")}

View File

@ -15,6 +15,15 @@ import { VariableDeclaration } from "farmbot";
import { clickButton } from "../../../__test_support__/helpers"; import { clickButton } from "../../../__test_support__/helpers";
import { Actions } from "../../../constants"; import { Actions } from "../../../constants";
const testVariable: VariableDeclaration = {
kind: "variable_declaration",
args: {
label: "label", data_value: {
kind: "identifier", args: { label: "new_var" }
}
}
};
describe("<ActiveEditor />", () => { describe("<ActiveEditor />", () => {
const fakeProps = (): ActiveEditorProps => ({ const fakeProps = (): ActiveEditorProps => ({
dispatch: jest.fn(), dispatch: jest.fn(),
@ -64,22 +73,55 @@ describe("<ActiveEditor />", () => {
type: Actions.SET_SCHEDULER_STATE, payload: true type: Actions.SET_SCHEDULER_STATE, payload: true
}); });
}); });
it("has correct height without variable form", () => {
const p = fakeProps();
p.regimen.body.body = [];
p.shouldDisplay = () => true;
const wrapper = mount(<ActiveEditor {...p} />);
expect(wrapper.find(".regimen").props().style).toEqual({
height: "calc(100vh - 200px)"
});
});
it("has correct height with variable form", () => {
const p = fakeProps();
p.regimen.body.body = [testVariable];
p.shouldDisplay = () => true;
const wrapper = mount(<ActiveEditor {...p} />);
expect(wrapper.find(".regimen").props().style)
.toEqual({ height: "calc(100vh - 500px)" });
});
it("has correct height with variable form collapsed", () => {
const p = fakeProps();
p.regimen.body.body = [testVariable];
p.shouldDisplay = () => true;
const wrapper = mount(<ActiveEditor {...p} />);
wrapper.setState({ variablesCollapsed: true });
expect(wrapper.find(".regimen").props().style)
.toEqual({ height: "calc(100vh - 300px)" });
});
it("automatically calculates height", () => {
document.getElementById = () => ({ offsetHeight: 101 } as HTMLElement);
const wrapper = mount(<ActiveEditor {...fakeProps()} />);
expect(wrapper.find(".regimen").props().style)
.toEqual({ height: "calc(100vh - 301px)" });
});
it("toggles variable form state", () => {
const wrapper = mount<ActiveEditor>(<ActiveEditor {...fakeProps()} />);
wrapper.instance().toggleVarShow();
expect(wrapper.state()).toEqual({ variablesCollapsed: true });
});
}); });
describe("editRegimenVariables()", () => { describe("editRegimenVariables()", () => {
const variables: VariableDeclaration = {
kind: "variable_declaration",
args: {
label: "label", data_value: {
kind: "identifier", args: { label: "new_var" }
}
}
};
it("updates bodyVariables", () => { it("updates bodyVariables", () => {
const regimen = fakeRegimen(); const regimen = fakeRegimen();
editRegimenVariables({ dispatch: jest.fn(), regimen })([])(variables); editRegimenVariables({ dispatch: jest.fn(), regimen })([])(testVariable);
expect(overwrite).toHaveBeenCalledWith(regimen, expect(overwrite).toHaveBeenCalledWith(regimen,
expect.objectContaining({ body: [variables] })); expect.objectContaining({ body: [testVariable] }));
}); });
}); });

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import { RegimenNameInput } from "./regimen_name_input"; import { RegimenNameInput } from "./regimen_name_input";
import { ActiveEditorProps } from "./interfaces"; import { ActiveEditorProps, ActiveEditorState } from "./interfaces";
import { push } from "../../history"; import { push } from "../../history";
import { import {
RegimenItem, CalendarRow, RegimenItemCalendarRow, RegimenProps RegimenItem, CalendarRow, RegimenItemCalendarRow, RegimenProps
@ -22,26 +22,47 @@ import { Actions } from "../../constants";
* The bottom half of the regimen editor panel (when there's something to * The bottom half of the regimen editor panel (when there's something to
* actually edit). * actually edit).
*/ */
export function ActiveEditor(props: ActiveEditorProps) { export class ActiveEditor
const regimenProps = { regimen: props.regimen, dispatch: props.dispatch }; extends React.Component<ActiveEditorProps, ActiveEditorState> {
return <div className="regimen-editor-content"> state: ActiveEditorState = { variablesCollapsed: false };
<div className="regimen-editor-tools">
<RegimenButtonGroup {...regimenProps} /> get regimenProps() {
<RegimenNameInput {...regimenProps} /> return { regimen: this.props.regimen, dispatch: this.props.dispatch };
<LocalsList }
locationDropdownKey={JSON.stringify(props.regimen)}
bodyVariables={props.regimen.body.body} toggleVarShow = () =>
variableData={props.variableData} this.setState({ variablesCollapsed: !this.state.variablesCollapsed });
sequenceUuid={props.regimen.uuid}
resources={props.resources} LocalsList = () => {
onChange={editRegimenVariables(regimenProps)(props.regimen.body.body)} const { regimen } = this.props;
allowedVariableNodes={AllowedVariableNodes.parameter} return <LocalsList
shouldDisplay={props.shouldDisplay} /> locationDropdownKey={JSON.stringify(regimen)}
<hr /> bodyVariables={regimen.body.body}
</div> variableData={this.props.variableData}
<OpenSchedulerButton dispatch={props.dispatch} /> sequenceUuid={regimen.uuid}
<RegimenRows calendar={props.calendar} dispatch={props.dispatch} /> resources={this.props.resources}
</div>; onChange={editRegimenVariables(this.regimenProps)(regimen.body.body)}
collapsible={true}
collapsed={this.state.variablesCollapsed}
toggleVarShow={this.toggleVarShow}
allowedVariableNodes={AllowedVariableNodes.parameter}
shouldDisplay={this.props.shouldDisplay} />;
}
render() {
return <div className="regimen-editor-content">
<div className="regimen-editor-tools">
<RegimenButtonGroup {...this.regimenProps} />
<RegimenNameInput {...this.regimenProps} />
<this.LocalsList />
<hr />
</div>
<OpenSchedulerButton dispatch={this.props.dispatch} />
<RegimenRows {...this.regimenProps}
calendar={this.props.calendar}
varsCollapsed={this.state.variablesCollapsed} />
</div>;
}
} }
export const OpenSchedulerButton = (props: { dispatch: Function }) => export const OpenSchedulerButton = (props: { dispatch: Function }) =>
@ -73,13 +94,29 @@ const RegimenButtonGroup = (props: RegimenProps) =>
</button> </button>
</div>; </div>;
/** Make room for the regimen header variable form when necessary. */
const regimenSectionHeight =
(regimen: TaggedRegimen, varsCollapsed: boolean) => {
let subHeight = 200;
const variables = regimen.body.body.length > 0;
if (variables) { subHeight = 500; }
if (varsCollapsed) { subHeight = 300; }
const variablesDiv = document.getElementById("regimen-editor-tools");
if (variablesDiv) { subHeight = 200 + variablesDiv.offsetHeight; }
return `calc(100vh - ${subHeight}px)`;
};
interface RegimenRowsProps { interface RegimenRowsProps {
regimen: TaggedRegimen;
calendar: CalendarRow[]; calendar: CalendarRow[];
dispatch: Function; dispatch: Function;
varsCollapsed: boolean;
} }
const RegimenRows = (props: RegimenRowsProps) => const RegimenRows = (props: RegimenRowsProps) =>
<div className="regimen"> <div className="regimen" style={{
height: regimenSectionHeight(props.regimen, props.varsCollapsed)
}}>
{props.calendar.map(regimenDay(props.dispatch))} {props.calendar.map(regimenDay(props.dispatch))}
</div>; </div>;

View File

@ -17,6 +17,10 @@ export interface ActiveEditorProps {
variableData: VariableNameSet; variableData: VariableNameSet;
} }
export interface ActiveEditorState {
variablesCollapsed: boolean;
}
export interface RegimenItemListProps { export interface RegimenItemListProps {
calendar: RegimenItemCalendarRow[]; calendar: RegimenItemCalendarRow[];
dispatch: Function; dispatch: Function;

View File

@ -0,0 +1,32 @@
import * as React from "react";
import { shallow } from "enzyme";
import { FilterSearch } from "../filter_search";
import { DropDownItem } from "../fb_select";
describe("<FilterSearch />", () => {
const fakeItem = (extra?: Partial<DropDownItem>): DropDownItem =>
Object.assign({ label: "label", value: "value" }, extra);
const fakeProps = () => ({
items: [],
selectedItem: fakeItem(),
onChange: jest.fn(),
nullChoice: fakeItem(),
});
it("selects item", () => {
const p = fakeProps();
const wrapper = shallow(<FilterSearch {...p} />);
const item = fakeItem();
wrapper.simulate("ItemSelect", item);
expect(p.onChange).toHaveBeenCalledWith(item);
});
it("doesn't select header", () => {
const p = fakeProps();
const wrapper = shallow(<FilterSearch {...p} />);
const item = fakeItem({ heading: true });
wrapper.simulate("ItemSelect", item);
expect(p.onChange).not.toHaveBeenCalled();
});
});

View File

@ -75,7 +75,7 @@ export class FilterSearch extends React.Component<Props, Partial<State>> {
} }
private handleValueChange = (item: DropDownItem | undefined) => { private handleValueChange = (item: DropDownItem | undefined) => {
if (item) { if (item && !item.heading) {
this.props.onChange(item); this.props.onChange(item);
this.setState({ item }); this.setState({ item });
} }