sensor/peripheral code cleanup

pull/1220/head
gabrielburnworth 2019-06-05 13:48:31 -07:00
parent fb48165d68
commit 901463ea11
11 changed files with 233 additions and 224 deletions

View File

@ -0,0 +1,80 @@
import * as React from "react";
import { shallow } from "enzyme";
import {
NameInputBox, PinDropdown, ModeDropdown, DeleteButton,
} from "../pin_form_fields";
import { fakeSensor } from "../../__test_support__/fake_state/resources";
import { Actions } from "../../constants";
import { FBSelect } from "../../ui";
const expectedPayload = (update: Object) =>
expect.objectContaining({
payload: expect.objectContaining({
update
}),
type: Actions.EDIT_RESOURCE
});
describe("<NameInputBox />", () => {
const fakeProps = () => ({
dispatch: jest.fn(),
value: undefined,
resource: fakeSensor(),
});
it("updates label", () => {
const p = fakeProps();
const wrapper = shallow(<NameInputBox {...p} />);
wrapper.find("input").simulate("change", {
currentTarget: { value: "GPIO 3" }
});
expect(p.dispatch).toHaveBeenCalledWith(
expectedPayload({ label: "GPIO 3" }));
});
});
describe("<PinDropdown />", () => {
const fakeProps = () => ({
dispatch: jest.fn(),
value: undefined,
resource: fakeSensor(),
});
it("updates pin", () => {
const p = fakeProps();
const wrapper = shallow(<PinDropdown {...p} />);
wrapper.find(FBSelect).simulate("change", { value: 3 });
expect(p.dispatch).toHaveBeenCalledWith(
expectedPayload({ pin: 3 }));
});
});
describe("<ModeDropdown />", () => {
const fakeProps = () => ({
dispatch: jest.fn(),
value: 1,
resource: fakeSensor(),
});
it("updates mode", () => {
const p = fakeProps();
const wrapper = shallow(<ModeDropdown {...p} />);
wrapper.find(FBSelect).simulate("change", { value: 0 });
expect(p.dispatch).toHaveBeenCalledWith(
expectedPayload({ mode: 0 }));
});
});
describe("<DeleteButton />", () => {
const fakeProps = () => ({
dispatch: jest.fn(),
uuid: "resource uuid",
});
it("deletes resource", () => {
const p = fakeProps();
const wrapper = shallow(<DeleteButton {...p} />);
wrapper.find("button").simulate("click");
expect(p.dispatch).toHaveBeenCalledWith(expect.any(Function));
});
});

View File

@ -47,10 +47,6 @@ export interface AxisInputBoxProps {
onChange: (key: string, val: number | undefined) => void;
}
export interface AxisInputBoxState {
value: string | undefined;
}
export interface ToggleButtonProps {
/** Function that is executed when the toggle button is clicked */
toggleAction: () => void;
@ -61,29 +57,3 @@ export interface ToggleButtonProps {
grayscale?: boolean;
title?: string;
}
export interface WebcamFeed {
id?: number;
url: string;
name: string;
updated_at?: string;
created_at?: string;
}
export interface Sensor {
id?: number;
pin: number | undefined;
label: string;
mode: number;
}
export interface SensorReading {
id?: number | undefined;
x: number | undefined;
y: number | undefined;
z: number | undefined;
value: number;
mode: number;
pin: number;
created_at: string;
}

View File

@ -1,9 +1,9 @@
import * as React from "react";
import { mount, shallow } from "enzyme";
import { mount } from "enzyme";
import { PeripheralForm } from "../peripheral_form";
import { TaggedPeripheral, SpecialStatus } from "farmbot";
import { PeripheralFormProps } from "../interfaces";
import { Actions } from "../../../constants";
import { NameInputBox, PinDropdown } from "../../pin_form_fields";
describe("<PeripheralForm/>", () => {
const dispatch = jest.fn();
@ -31,43 +31,13 @@ describe("<PeripheralForm/>", () => {
];
const fakeProps = (): PeripheralFormProps => ({ dispatch, peripherals });
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}
peripherals={peripherals} />);
const inputs = form.find("input");
expect(inputs.at(0).props().value).toEqual("GPIO 2");
expect(inputs.at(1).props().value).toEqual("GPIO 13 - LED");
});
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");
expect(p.dispatch).toHaveBeenCalledWith(expect.any(Function));
const form = mount(<PeripheralForm {...fakeProps()} />);
const sensorNames = form.find(NameInputBox);
expect(sensorNames.at(0).props().value).toEqual("GPIO 2");
expect(sensorNames.at(1).props().value).toEqual("GPIO 13 - LED");
const sensorPins = form.find(PinDropdown);
expect(sensorPins.at(0).props().value).toEqual(2);
expect(sensorPins.at(1).props().value).toEqual(13);
});
});

View File

@ -1,44 +1,30 @@
import * as React from "react";
import { destroy, edit } from "../../api/crud";
import { PeripheralFormProps } from "./interfaces";
import { sortResourcesById } from "../../util";
import { t } from "../../i18next_wrapper";
import { Row, Col, FBSelect } from "../../ui";
import {
pinDropdowns
} from "../../sequences/step_tiles/pin_and_peripheral_support";
import { Row, Col } from "../../ui";
import { NameInputBox, PinDropdown, DeleteButton } from "../pin_form_fields";
export function PeripheralForm(props: PeripheralFormProps) {
const { dispatch, peripherals } = props;
return <div>
{sortResourcesById(peripherals).map(p => {
return <Row key={p.uuid + p.body.id}>
export const PeripheralForm = (props: PeripheralFormProps) =>
<div className="peripheral-form">
{sortResourcesById(props.peripherals).map(peripheral =>
<Row key={peripheral.uuid}>
<Col xs={6}>
<input type="text"
placeholder={t("Name")}
value={p.body.label}
onChange={e =>
dispatch(edit(p, { label: e.currentTarget.value }))} />
<NameInputBox
dispatch={props.dispatch}
value={peripheral.body.label}
resource={peripheral} />
</Col>
<Col xs={4}>
<FBSelect
selectedItem={{
label: t("Pin ") + `${p.body.pin}`,
value: p.body.pin || ""
}}
onChange={d =>
dispatch(edit(p, { pin: parseInt(d.value.toString(), 10) }))}
list={pinDropdowns(n => n)} />
<PinDropdown
dispatch={props.dispatch}
value={peripheral.body.pin}
resource={peripheral} />
</Col>
<Col xs={2}>
<button
className="red fb-button"
onClick={() => dispatch(destroy(p.uuid))}>
<i className="fa fa-minus" />
</button>
<DeleteButton
dispatch={props.dispatch}
uuid={peripheral.uuid} />
</Col>
</Row>;
})}
</Row>
)}
</div>;
}

View File

@ -5,19 +5,16 @@ import { sortResourcesById } from "../../util";
import { KeyValShowRow } from "../key_val_show_row";
import { t } from "../../i18next_wrapper";
export function PeripheralList(props: PeripheralListProps) {
const { pins, disabled } = props;
return <div>
{sortResourcesById(props.peripherals).map(p => {
return <KeyValShowRow key={p.uuid}
export const PeripheralList = (props: PeripheralListProps) =>
<div className="peripheral-list">
{sortResourcesById(props.peripherals).map(p =>
<KeyValShowRow key={p.uuid}
label={p.body.label}
labelPlaceholder=""
value={"" + p.body.pin}
toggleValue={(pins[p.body.pin || -1] || { value: undefined }).value}
toggleValue={(props.pins[p.body.pin || -1] || { value: undefined }).value}
valuePlaceholder=""
title={t(`Toggle ${p.body.label}`)}
onClick={() => p.body.pin && pinToggle(p.body.pin)}
disabled={!!disabled} />;
})}
disabled={!!props.disabled} />)}
</div>;
}

View File

@ -0,0 +1,70 @@
import * as React from "react";
import { destroy, edit } from "../api/crud";
import { FBSelect } from "../ui";
import {
pinDropdowns
} from "../sequences/step_tiles/pin_and_peripheral_support";
import { PIN_MODES } from "../sequences/step_tiles/tile_pin_support";
import { t } from "../i18next_wrapper";
import { TaggedPeripheral, TaggedSensor } from "farmbot";
import { UUID } from "../resources/interfaces";
const MODES: { [s: string]: string } = {
0: t("Digital"),
1: t("Analog")
};
interface NameInputBoxProps {
dispatch: Function;
value: string | undefined;
resource: TaggedPeripheral | TaggedSensor;
}
export const NameInputBox = (props: NameInputBoxProps) =>
<input type="text"
placeholder={t("Name")}
value={props.value}
onChange={e => props.dispatch(edit(props.resource, {
label: e.currentTarget.value
}))} />;
interface PinDropdownProps {
dispatch: Function;
value: number | undefined;
resource: TaggedPeripheral | TaggedSensor;
}
export const PinDropdown = (props: PinDropdownProps) =>
<FBSelect
selectedItem={
{ label: t("Pin ") + `${props.value}`, value: props.value || "" }}
onChange={d => props.dispatch(edit(props.resource, {
pin: parseInt(d.value.toString(), 10)
}))}
list={pinDropdowns(n => n)} />;
interface ModeDropdownProps {
dispatch: Function;
value: number;
resource: TaggedPeripheral | TaggedSensor;
}
export const ModeDropdown = (props: ModeDropdownProps) =>
<FBSelect
onChange={d => {
props.dispatch(edit(props.resource, { mode: parseInt(d.value.toString(), 10) }));
}}
selectedItem={{ label: MODES[props.value], value: props.value }}
list={PIN_MODES} />;
interface DeleteButtonProps {
dispatch: Function;
uuid: UUID;
}
export const DeleteButton = (props: DeleteButtonProps) =>
<button
className="red fb-button"
onClick={() => props.dispatch(destroy(props.uuid))}>
<i className="fa fa-minus" />
</button>;

View File

@ -1,9 +1,9 @@
import * as React from "react";
import { shallow } from "enzyme";
import { SensorForm } from "../sensor_form";
import { Actions } from "../../../constants";
import { SensorFormProps } from "../interfaces";
import { fakeSensor } from "../../../__test_support__/fake_state/resources";
import { NameInputBox, PinDropdown, ModeDropdown } from "../../pin_form_fields";
describe("<SensorForm/>", function () {
const fakeProps = (): SensorFormProps => {
@ -21,49 +21,16 @@ describe("<SensorForm/>", function () {
};
};
const expectedPayload = (update: Object) =>
expect.objectContaining({
payload: expect.objectContaining({
update
}),
type: Actions.EDIT_RESOURCE
});
it("renders a list of editable sensors, in sorted order", () => {
const form = shallow(<SensorForm {...fakeProps()} />);
const inputs = form.find("input");
expect(inputs.at(0).props().value).toEqual("GPIO 51");
expect(inputs.at(1).props().value).toEqual("GPIO 50 - Moisture");
});
it("updates label", () => {
const p = fakeProps();
const form = shallow(<SensorForm {...p} />);
const inputs = form.find("input");
inputs.at(0).simulate("change", { currentTarget: { value: "GPIO 52" } });
expect(p.dispatch).toHaveBeenCalledWith(
expectedPayload({ label: "GPIO 52" }));
});
it("updates pin", () => {
const p = fakeProps();
const form = shallow(<SensorForm {...p} />);
form.find("FBSelect").at(0).simulate("change", { value: 52 });
expect(p.dispatch).toHaveBeenCalledWith(expectedPayload({ pin: 52 }));
});
it("updates mode", () => {
const p = fakeProps();
const form = shallow(<SensorForm {...p} />);
form.find("FBSelect").at(1).simulate("change", { value: 0 });
expect(p.dispatch).toHaveBeenCalledWith(expectedPayload({ mode: 0 }));
});
it("deletes sensor", () => {
const p = fakeProps();
const form = shallow(<SensorForm {...p} />);
const buttons = form.find("button");
buttons.at(0).simulate("click");
expect(p.dispatch).toHaveBeenCalledWith(expect.any(Function));
const sensorNames = form.find(NameInputBox);
expect(sensorNames.at(0).props().value).toEqual("GPIO 51");
expect(sensorNames.at(1).props().value).toEqual("GPIO 50 - Moisture");
const sensorPins = form.find(PinDropdown);
expect(sensorPins.at(0).props().value).toEqual(51);
expect(sensorPins.at(1).props().value).toEqual(50);
const sensorModes = form.find(ModeDropdown);
expect(sensorModes.at(0).props().value).toEqual(0);
expect(sensorModes.at(1).props().value).toEqual(0);
});
});

View File

@ -5,13 +5,6 @@ export interface SensorState {
isEditing: boolean;
}
export interface Sensor {
id?: number;
pin: number | undefined;
mode: number;
label: string;
}
export interface SensorFormProps {
dispatch: Function;
sensors: TaggedSensor[];

View File

@ -1,57 +1,37 @@
import * as React from "react";
import { destroy, edit } from "../../api/crud";
import { SensorFormProps } from "./interfaces";
import { sortResourcesById } from "../../util";
import { Row, Col, FBSelect } from "../../ui";
import { Row, Col } from "../../ui";
import {
pinDropdowns
} from "../../sequences/step_tiles/pin_and_peripheral_support";
import { PIN_MODES } from "../../sequences/step_tiles/tile_pin_support";
import { t } from "../../i18next_wrapper";
NameInputBox, PinDropdown, ModeDropdown, DeleteButton
} from "../pin_form_fields";
export function SensorForm(props: SensorFormProps) {
const { dispatch, sensors } = props;
const modes: { [s: string]: string } = {
0: t("Digital"),
1: t("Analog")
};
return <div>
{sortResourcesById(sensors).map(p => {
return <Row key={p.uuid + p.body.id}>
export const SensorForm = (props: SensorFormProps) =>
<div className="sensor-form">
{sortResourcesById(props.sensors).map(sensor =>
<Row key={sensor.uuid}>
<Col xs={4}>
<input type="text"
placeholder={t("Name")}
value={p.body.label}
onChange={e => dispatch(edit(p, {
label: e.currentTarget.value
}))} />
<NameInputBox
dispatch={props.dispatch}
value={sensor.body.label}
resource={sensor} />
</Col>
<Col xs={3}>
<FBSelect
selectedItem={
{ label: t("Pin ") + `${p.body.pin}`, value: p.body.pin || "" }}
onChange={d => dispatch(edit(p, {
pin: parseInt(d.value.toString(), 10)
}))}
list={pinDropdowns(n => n)} />
<PinDropdown
dispatch={props.dispatch}
value={sensor.body.pin}
resource={sensor} />
</Col>
<Col xs={3}>
<FBSelect
onChange={d => {
dispatch(edit(p, { mode: parseInt(d.value.toString(), 10) }));
}}
selectedItem={{ label: modes[p.body.mode], value: p.body.mode }}
list={PIN_MODES} />
<ModeDropdown
dispatch={props.dispatch}
value={sensor.body.mode}
resource={sensor} />
</Col>
<Col xs={2}>
<button
className="red fb-button"
onClick={() => dispatch(destroy(p.uuid))}>
<i className="fa fa-minus" />
</button>
<DeleteButton
dispatch={props.dispatch}
uuid={sensor.uuid} />
</Col>
</Row>;
})}
</Row>)}
</div>;
}

View File

@ -3,14 +3,18 @@ import { readPin } from "../../devices/actions";
import { SensorListProps } from "./interfaces";
import { sortResourcesById } from "../../util";
import { Row, Col } from "../../ui";
import { isNumber } from "lodash";
import { ALLOWED_PIN_MODES } from "farmbot";
import { t } from "../../i18next_wrapper";
interface SensorReadingDisplayProps {
label: string;
value: number | undefined;
mode: number;
}
const SensorReadingDisplay =
({ label, value, mode }:
{ label: string, value: number | undefined, mode: number }) => {
({ label, value, mode }: SensorReadingDisplayProps) => {
const moistureSensor = label.toLowerCase().includes("moisture") ?
"moisture-sensor" : "";
return <div className={`sensor-reading-display ${moistureSensor}`}>
@ -36,18 +40,18 @@ const SensorReadingDisplay =
</div>;
};
export function SensorList(props: SensorListProps) {
const { pins, disabled } = props;
return <div>
export const SensorList = (props: SensorListProps) =>
<div className="sensor-list">
{sortResourcesById(props.sensors).map(p => {
const { label, mode, pin } = p.body;
const value = (pins[pin || -1] || { value: undefined }).value;
const pinNumber = (isNumber(pin) && isFinite(pin)) ? pin : -1;
const value = (props.pins[pinNumber] || { value: undefined }).value;
return <Row key={p.uuid + p.body.id}>
<Col xs={3}>
<label>{label}</label>
</Col>
<Col xs={1}>
<p>{pin}</p>
<p>{pinNumber}</p>
</Col>
<Col xs={6}>
<SensorReadingDisplay label={label} value={value} mode={mode} />
@ -55,13 +59,13 @@ export function SensorList(props: SensorListProps) {
<Col xs={2}>
<button
className={"fb-button gray"}
disabled={disabled}
disabled={props.disabled}
title={t(`read ${label} sensor`)}
onClick={() => readPin(pin || 0, `pin${pin}`, mode as ALLOWED_PIN_MODES)}>
onClick={() =>
readPin(pinNumber, `pin${pin}`, mode as ALLOWED_PIN_MODES)}>
{t("read sensor")}
</button>
</Col>
</Row>;
})}
</div>;
}

View File

@ -1,8 +1,4 @@
import {
RegimenProps,
CalendarRow,
RegimenItemCalendarRow
} from "../interfaces";
import { CalendarRow, RegimenItemCalendarRow } from "../interfaces";
import { TaggedRegimen } from "farmbot";
import { Actions } from "../../constants";
import { ResourceIndex, VariableNameSet } from "../../resources/interfaces";
@ -50,10 +46,6 @@ export interface CopyButtonProps {
regimen: TaggedRegimen;
}
export interface DeleteButtonProps extends RegimenProps {
baseUrl: string;
}
export interface SelectRegimen {
type: Actions.SELECT_REGIMEN;
payload: string;