cleanup and refactoring

pull/1652/head
gabrielburnworth 2020-01-03 12:13:49 -08:00
parent 9d7833b71c
commit 781ac33b10
31 changed files with 211 additions and 323 deletions

View File

@ -26,6 +26,7 @@ module CeleryScript
FRIENDLY_ERRORS = {
nothing: {
write_pin: "You must select a Peripheral in the Control Peripheral step.",
toggle_pin: "You must select a Peripheral in the Toggle Peripheral step.",
variable_declaration: MISSING_VAR,
parameter_declaration: MISSING_PARAM,
read_pin: "You must select a Sensor in the Read Sensor step.",

View File

@ -300,7 +300,9 @@ export namespace ToolTips {
trim(`Power cycle FarmBot's onboard computer or microcontroller.`);
export const SET_SERVO_ANGLE =
trim(`Move a servo to the provided angle.`);
trim(`Move a servo to the provided angle. An angle of 90 degrees
corresponds to the servo midpoint (or, for a continuous rotation
servo, no movement).`);
export const TOGGLE_PIN =
trim(`Toggle a digital pin on or off.`);

View File

@ -23,6 +23,12 @@
margin-left: 1rem;
box-shadow: none;
}
input[type="radio"] {
margin-right: 0.25rem;
margin-bottom: 0.25rem;
margin-top: 0;
vertical-align: middle;
}
}
}
}
@ -107,7 +113,7 @@
background: $blue;
}
&.toggle-pin-step {
background: $yellow;
background: $orange;
}
&.set-zero-step {
background: $blue;
@ -124,6 +130,12 @@
&.system-action-step {
background: $brown;
}
&.emergency-stop-step {
background: $brown;
}
&.reboot-step {
background: $brown;
}
&.unknown-step {
background: $gray;
}
@ -221,7 +233,7 @@
background: $light_blue;
}
&.toggle-pin-step {
background: $light_yellow;
background: $light_orange;
}
&.set-zero-step {
background: $light_blue;
@ -238,6 +250,12 @@
&.system-action-step {
background: $light_brown;
}
&.emergency-stop-step {
background: $light_brown;
}
&.reboot-step {
background: $light_brown;
}
&.unknown-step {
background: $light_gray;
}

View File

@ -6,7 +6,7 @@ jest.mock("../../draggable/actions", () => ({
stepGet: jest.fn(() => () => mockStepGetResult),
}));
import { FolderNode } from "../constants";
import { FolderNode } from "../interfaces";
import { ingest } from "../data_transfer";
import {
collapseAll,
@ -240,11 +240,11 @@ describe("deleteFolder", () => {
describe("updateSearchTerm", () => {
it("updates a search term", () => {
const argss =
const args =
(payload: string | undefined) => ({ type: "FOLDER_SEARCH", payload });
[undefined, "foo"].map(term => {
updateSearchTerm(term);
expect(store.dispatch).toHaveBeenCalledWith(argss(term));
expect(store.dispatch).toHaveBeenCalledWith(args(term));
});
});
});

View File

@ -35,7 +35,7 @@ import {
FolderNameInputProps,
FolderNodeMedial,
FolderNodeTerminal,
} from "../constants";
} from "../interfaces";
import {
updateSearchTerm, toggleAll, moveSequence, dropSequence,
sequenceEditMaybeSave,

View File

@ -1,4 +1,4 @@
import { FolderNode } from "../constants";
import { FolderNode } from "../interfaces";
import { ingest } from "../data_transfer";
import { climb } from "../climb";

View File

@ -1,7 +1,7 @@
import { TEST_GRAPH } from "./actions_test";
import { searchFolderTree, FolderSearchProps } from "../search_folder_tree";
import { TaggedResource } from "farmbot";
import { FolderUnion } from "../constants";
import { FolderUnion } from "../interfaces";
describe("searchFolderTree", () => {
const searchFor = (input: string) => searchFolderTree({
@ -19,7 +19,7 @@ describe("searchFolderTree", () => {
expect(before).toEqual(after); // Prevent mutation of original data.
});
it("finds an `inital` folder", () => {
it("finds an `initial` folder", () => {
const results = searchFor("one").map(x => x.name);
expect(results.length).toEqual(1);
expect(results).toContain("One");

View File

@ -1,4 +1,4 @@
import { RootFolderNode as Tree } from "./constants";
import { RootFolderNode as Tree } from "./interfaces";
import { cloneAndClimb } from "./climb";
import { Color, SpecialStatus, TaggedSequence } from "farmbot";
import { store } from "../redux/store";

View File

@ -1,4 +1,6 @@
import { RootFolderNode, FolderUnion, FolderNodeMedial, FolderNodeInitial } from "./constants";
import {
RootFolderNode, FolderUnion, FolderNodeMedial, FolderNodeInitial
} from "./interfaces";
import { defensiveClone } from "../util";
interface TreeClimberState {

View File

@ -20,7 +20,7 @@ import {
FolderButtonClusterProps,
FolderNameInputProps,
SequenceDropAreaState,
} from "./constants";
} from "./interfaces";
import {
createFolder,
deleteFolder,

View File

@ -4,7 +4,7 @@ import {
FolderNodeTerminal,
RootFolderNode,
FolderMeta,
} from "./constants";
} from "./interfaces";
import { sortBy } from "lodash";
type FoldersIndexedByParentId = Record<number, FolderNode[]>;

View File

@ -1,4 +1,4 @@
import { FolderProps } from "./constants";
import { FolderProps } from "./interfaces";
import { selectAllSequences } from "../resources/selectors";
import { TaggedSequence } from "farmbot";
import { resourceUsageList } from "../resources/in_use";

View File

@ -1,5 +1,5 @@
import { TaggedResource, TaggedSequence } from "farmbot";
import { RootFolderNode, FolderUnion } from "./constants";
import { RootFolderNode, FolderUnion } from "./interfaces";
export interface FolderSearchProps {
references: Record<string, TaggedResource | undefined>;

View File

@ -14,7 +14,7 @@ import { HelpState } from "../help/reducer";
import { UsageIndex } from "./in_use";
import { SequenceMeta } from "./sequence_meta";
import { AlertReducerState } from "../messages/interfaces";
import { RootFolderNode, FolderMeta } from "../folders/constants";
import { RootFolderNode, FolderMeta } from "../folders/interfaces";
export type UUID = string;
export type VariableNameSet = Record<string, SequenceMeta | undefined>;

View File

@ -33,7 +33,9 @@ import { get } from "lodash";
import { Actions } from "../constants";
import { getFbosConfig } from "./getters";
import { ingest, PARENTLESS as NO_PARENT } from "../folders/data_transfer";
import { FolderNode, FolderMeta, FolderNodeTerminal, FolderNodeMedial } from "../folders/constants";
import {
FolderNode, FolderMeta, FolderNodeTerminal, FolderNodeMedial
} from "../folders/interfaces";
import { climb } from "../folders/climb";
export function findByUuid(index: ResourceIndex, uuid: string): TaggedResource {

View File

@ -52,7 +52,7 @@ export function StepButtonCluster(props: StepButtonProps) {
step={{
kind: "toggle_pin",
args: {
pin_number: 4
pin_number: NOTHING_SELECTED
}
}}
color="orange">

View File

@ -97,61 +97,6 @@ describe("renderCeleryNode()", () => {
}
const TEST_DATA: TestData[] = [
{
expected: "MarkPlantasx = 300",
node: {
kind: "resource_update",
args: {
resource_id: 23,
resource_type: "Plant",
label: "x",
value: 300
}
}
},
{
expected: "System",
node: {
kind: "check_updates",
args: {
package: "farmbot_os"
}
}
},
{
expected: "System",
node: {
kind: "factory_reset",
args: {
package: "farmbot_os"
}
}
},
{
expected: "Unlocking a device requires user intervention",
node: {
kind: "emergency_lock",
args: {}
}
},
{
expected: "Unable to properly display this step",
node: {
kind: "power_off",
args: {}
}
},
{
expected: "Unable to properly display this step",
node: { kind: "read_status", args: {} }
},
{
expected: "",
node: {
kind: "install_first_party_farmware",
args: {}
}
},
{
node: {
kind: "_if",
@ -166,31 +111,13 @@ describe("renderCeleryNode()", () => {
expected: "Then Execute"
},
{
node: {
kind: "execute_script",
args: { label: "farmware-to-execute" }
},
node: { kind: "execute_script", args: { label: "farmware-to-execute" } },
expected: "Manual Input"
},
{
node: {
kind: "execute",
args: {
sequence_id: 0
}
},
node: { kind: "execute", args: { sequence_id: 0 } },
expected: "Select a sequence"
},
{
node: {
kind: "find_home",
args: {
speed: 100,
axis: "all"
}
},
expected: "Find x"
},
{
node: {
kind: "move_absolute",
@ -213,40 +140,37 @@ describe("renderCeleryNode()", () => {
expected: "Message"
},
{
node: {
kind: "take_photo",
args: {}
},
node: { kind: "take_photo", args: {} },
expected: "Photo"
},
{
node: {
kind: "wait",
args: {
milliseconds: 100
}
},
node: { kind: "wait", args: { milliseconds: 100 } },
expected: "milliseconds"
},
{
node: {
kind: "set_servo_angle",
kind: "resource_update",
args: {
pin_number: 4,
pin_value: 90,
resource_id: 23,
resource_type: "Plant",
label: "x",
value: 300
}
},
expected: "MarkPlantasx = 300"
},
{
node: { kind: "set_servo_angle", args: { pin_number: 4, pin_value: 90, } },
expected: "Servo"
},
{
node: {
kind: "toggle_pin",
args: {
pin_number: 13
}
},
node: { kind: "toggle_pin", args: { pin_number: 13 } },
expected: "Pin"
},
{
node: { kind: "find_home", args: { speed: 100, axis: "all" } },
expected: "Find x"
},
{
node: { kind: "zero", args: { axis: "all" } },
expected: "Zero x"
@ -259,52 +183,36 @@ describe("renderCeleryNode()", () => {
node: { kind: "home", args: { axis: "all", speed: 100, } },
expected: "Home x"
},
{
node: { kind: "reboot", args: { package: "farmbot_os" } },
expected: "System"
},
{
node: {
kind: "check_updates", args: { package: "farmbot_os" }
},
expected: "System"
},
{
node: {
kind: "factory_reset", args: { package: "farmbot_os" }
},
expected: "System"
},
{
node: {
kind: "sync",
args: {}
},
expected: ""
},
{
node: {
kind: "dump_info",
args: {}
},
expected: ""
},
{
node: {
kind: "power_off",
args: {}
},
expected: ""
},
{
node: {
kind: "read_status",
args: {}
},
expected: ""
},
{
node: { kind: "emergency_lock", args: {} },
expected: "Unlocking a device requires user intervention"
},
{
node: { kind: "reboot", args: { package: "farmbot_os" } },
expected: "entire system"
},
{
node: { kind: "check_updates", args: { package: "farmbot_os" } },
expected: "System"
},
{
node: { kind: "factory_reset", args: { package: "farmbot_os" } },
expected: "System"
},
{
node: { kind: "sync", args: {} },
expected: ""
},
{
node: { kind: "dump_info", args: {} },
expected: ""
},
{
node: { kind: "power_off", args: {} },
expected: ""
},
{
node: { kind: "read_status", args: {} },
expected: ""
},
{
@ -312,19 +220,12 @@ describe("renderCeleryNode()", () => {
expected: ""
},
{
node: {
kind: "install_first_party_farmware", args: {}
},
node: { kind: "install_first_party_farmware", args: {} },
expected: ""
},
{
node: {
kind: "unknown",
args: {
unknown: 0
}
// tslint:disable-next-line:no-any
} as any,
// tslint:disable-next-line: no-any
node: { kind: "unknown", args: { unknown: 0 } } as any,
expected: "unknown"
},
];

View File

@ -1,13 +1,12 @@
jest.mock("../../api/crud", () => {
return { editStep: jest.fn() };
});
import { TileReboot, editTheRebootStep, rebootExecutor, MultiChoiceRadio } from "../step_tiles/tile_reboot";
import { render, mount } from "enzyme";
jest.mock("../../../api/crud", () => ({ editStep: jest.fn() }));
import { TileReboot, editTheRebootStep, rebootExecutor } from "../tile_reboot";
import { render } from "enzyme";
import React from "react";
import { StepParams } from "../interfaces";
import { fakeSequence } from "../../__test_support__/fake_state/resources";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
import { editStep } from "../../api/crud";
import { StepParams } from "../../interfaces";
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
import { editStep } from "../../../api/crud";
import { Reboot } from "farmbot";
const fakeProps = (): StepParams => {
@ -66,21 +65,3 @@ describe("<TileReboot/>", () => {
expect(step.args.package).toBe("arduino_firmware");
});
});
describe("MultiChoiceRadio", () => {
it("triggers callbacks", () => {
const PACKAGE_CHOICES = {
"a": "1",
"b": "2"
};
const onChange = jest.fn();
const el = mount(<MultiChoiceRadio
uuid={"WHATEVER"}
choices={PACKAGE_CHOICES}
currentChoice={"a"}
onChange={onChange} />);
const radio = el.find("input[type='radio']").first();
radio.simulate("change", "a");
expect(onChange).toHaveBeenCalledWith("a");
});
});

View File

@ -1,7 +1,9 @@
jest.mock("../../../api/crud", () => ({ editStep: jest.fn() }));
import * as React from "react";
import { TileSetServoAngle, pinNumberChanger, createServoEditFn, ServoPinSelection } from "../tile_set_servo_angle";
import {
TileSetServoAngle, pinNumberChanger, createServoEditFn, ServoPinSelection
} from "../tile_set_servo_angle";
import { mount } from "enzyme";
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
import { SetServoAngle } from "farmbot";
@ -33,8 +35,8 @@ describe("<TileSetServoAngle/>", () => {
const inputs = block.find("input");
const labels = block.find("label");
const stepArgs = props.currentStep.args as SetServoAngle["args"];
expect(inputs.length).toEqual(4);
expect(labels.length).toEqual(4);
expect(inputs.length).toEqual(6);
expect(labels.length).toEqual(6);
expect(inputs.first().props().placeholder).toEqual("Control Servo");
expect(labels.at(0).text()).toContain("Servo angle (0-180)");
expect(inputs.at(1).props().value).toEqual(stepArgs.pin_value);
@ -53,7 +55,7 @@ describe("<TileSetServoAngle/>", () => {
});
});
it("dissallows named_pins", () => {
it("disallows named_pins", () => {
const p = fakeProps();
const step = p.currentStep;
if (step.kind === "set_servo_angle") {

View File

@ -157,10 +157,11 @@ export function renderCeleryNode(props: StepParams) {
case "home": return <TileMoveHome {...props} />;
case "reboot": return <TileReboot {...props} />;
case "emergency_lock": return <TileEmergencyStop {...props} />;
case "install_first_party_farmware": return <TileSystemAction {...props} />;
case "assertion": return <TileAssertion {...props} />;
case "check_updates":
case "factory_reset":
case "sync": case "dump_info": case "power_off": case "read_status":
case "emergency_unlock": case "install_first_party_farmware":
return <TileSystemAction {...props} />;
case "check_updates": case "factory_reset":
return <TileFirmwareAction {...props} />;
default:
return <TileUnknown {...props} />;

View File

@ -4,7 +4,7 @@ import { ToolTips, Content } from "../../constants";
import {
StepWrapper, StepHeader, StepContent, conflictsString, StepWarning
} from "../step_ui/index";
import { StepRadio } from "../step_ui/step_radio";
import { AxisStepRadio } from "../step_ui/step_radio";
import { t } from "../../i18next_wrapper";
import { Xyz, Calibrate, TaggedSequence } from "farmbot";
import { some } from "lodash";
@ -81,7 +81,7 @@ class InnerTileCalibrate extends React.Component<CalibrateParams, {}> {
conflicts={this.settingConflicts} />}
</StepHeader>
<StepContent className={className}>
<StepRadio
<AxisStepRadio
currentSequence={currentSequence}
currentStep={currentStep}
dispatch={dispatch}

View File

@ -7,7 +7,7 @@ import { t } from "../../i18next_wrapper";
export function TileEmergencyStop(props: StepParams) {
const { dispatch, currentStep, index, currentSequence } = props;
const className = "take-photo-step";
const className = "emergency-stop-step";
return <StepWrapper>
<StepHeader
className={className}

View File

@ -7,7 +7,7 @@ import {
StepWrapper, StepHeader, StepContent, StepWarning, conflictsString
} from "../step_ui/index";
import { some } from "lodash";
import { StepRadio } from "../step_ui/step_radio";
import { AxisStepRadio } from "../step_ui/step_radio";
import { t } from "../../i18next_wrapper";
export function TileFindHome(props: StepParams) {
@ -80,7 +80,7 @@ class InnerFindHome extends React.Component<FindHomeParams, {}> {
conflicts={this.settingConflicts} />}
</StepHeader>
<StepContent className={className}>
<StepRadio
<AxisStepRadio
currentSequence={currentSequence}
currentStep={currentStep}
dispatch={dispatch}

View File

@ -2,10 +2,11 @@ import * as React from "react";
import { StepParams } from "../interfaces";
import { ToolTips } from "../../constants";
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
import { StepRadio } from "../step_ui/step_radio";
import { AxisStepRadio } from "../step_ui/step_radio";
import { StepInputBox } from "../inputs/step_input_box";
import { Row, Col } from "../../ui";
import { t } from "../../i18next_wrapper";
import { Home } from "farmbot";
export function TileMoveHome(props: StepParams) {
const { dispatch, currentStep, index, currentSequence } = props;
@ -20,9 +21,9 @@ export function TileMoveHome(props: StepParams) {
index={index}
confirmStepDeletion={props.confirmStepDeletion} />
<StepContent className={className}>
<StepRadio
<AxisStepRadio
currentSequence={currentSequence}
currentStep={currentStep}
currentStep={currentStep as Home}
dispatch={dispatch}
index={index}
label={t("Home")} />

View File

@ -3,52 +3,18 @@ import { StepParams } from "../interfaces";
import { ToolTips } from "../../constants";
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
import { t } from "../../i18next_wrapper";
import { Row, Col } from "../../ui";
import { ALLOWED_PACKAGES, SequenceBodyItem, Reboot } from "farmbot";
import { editStep } from "../../api/crud";
import { StepRadio } from "../step_ui/step_radio";
type StringMap = Record<string, string>;
interface MultiChoiceRadioProps<T extends StringMap> {
uuid: string;
choices: T;
currentChoice: keyof T;
onChange(key: keyof T): void;
}
export const MultiChoiceRadio =
<T extends StringMap>(props: MultiChoiceRadioProps<T>) => {
const choices = Object.keys(props.choices);
return <Row>
<Col xs={12}>
<div className="bottom-content">
<div className="channel-fields">
<form>
{choices.map((choice, i) =>
<div key={`${props.uuid} ${i}`} style={{ display: "inline" }}>
<label>
<input type="radio"
value={choice}
onChange={() => props.onChange(choice)}
checked={props.currentChoice === choice} />
{t(props.choices[choice])}
</label>
</div>)}
</form>
</div>
</div>
</Col>
</Row>;
};
const PACKAGE_CHOICES: Record<ALLOWED_PACKAGES, string> = {
"arduino_firmware": "Just the Arduino",
"farmbot_os": "Entire system"
};
const PACKAGE_CHOICES = (): Record<ALLOWED_PACKAGES, string> => ({
"arduino_firmware": t("Just the Arduino"),
"farmbot_os": t("Entire system")
});
function assertReboot(x: SequenceBodyItem): asserts x is Reboot {
if (x.kind !== "reboot") {
throw new Error("Impossible");
throw new Error(`${x.kind} is not "reboot"`);
}
}
@ -74,7 +40,7 @@ export const editTheRebootStep =
export function TileReboot(props: StepParams) {
const { dispatch, currentStep, index, currentSequence } = props;
const className = "take-photo-step";
const className = "reboot-step";
assertReboot(currentStep);
return <StepWrapper>
<StepHeader
@ -86,9 +52,9 @@ export function TileReboot(props: StepParams) {
index={index}
confirmStepDeletion={props.confirmStepDeletion} />
<StepContent className={className}>
<MultiChoiceRadio
uuid={currentSequence.uuid + index}
choices={PACKAGE_CHOICES}
<StepRadio
choices={Object.keys(PACKAGE_CHOICES())}
choiceLabelLookup={PACKAGE_CHOICES()}
currentChoice={currentStep.args.package as ALLOWED_PACKAGES}
onChange={editTheRebootStep(props)} />
</StepContent>

View File

@ -2,17 +2,18 @@ import * as React from "react";
import { StepInputBox } from "../inputs/step_input_box";
import { StepParams } from "../interfaces";
import { ToolTips } from "../../constants";
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
import { StepWrapper, StepHeader, StepContent } from "../step_ui";
import { Row, Col } from "../../ui/index";
import { t } from "../../i18next_wrapper";
import { MultiChoiceRadio } from "./tile_reboot";
import { SetServoAngle } from "farmbot";
import { editStep } from "../../api/crud";
import { StepRadio } from "../step_ui/step_radio";
const PACKAGE_CHOICES: Record<string, string> = {
"4": "Pin 4",
"5": "Pin 5",
};
const PIN_CHOICES = ["4", "5", "6", "11"];
const CHOICE_LABELS = () => PIN_CHOICES.reduce((acc, pinNumber) => {
acc[pinNumber] = `${t("Pin")} ${pinNumber}`;
return acc;
}, {} as Record<string, string>);
type Keys =
| "dispatch"
@ -35,14 +36,14 @@ export const pinNumberChanger = (props: Props) => (y: string) => {
};
export function ServoPinSelection(props: Props) {
const { currentSequence, index, currentStep } = props;
const { currentStep } = props;
const num = (currentStep as SetServoAngle).args.pin_number;
if (typeof num !== "number") { throw new Error("NO!"); }
const onChange = pinNumberChanger(props);
return <MultiChoiceRadio
uuid={currentSequence.uuid + index}
choices={PACKAGE_CHOICES}
return <StepRadio
choices={PIN_CHOICES}
choiceLabelLookup={CHOICE_LABELS()}
currentChoice={"" + num}
onChange={onChange} />;
}
@ -79,5 +80,4 @@ export function TileSetServoAngle(props: StepParams) {
</Row>
</StepContent>
</StepWrapper>;
}

View File

@ -2,8 +2,9 @@ import * as React from "react";
import { StepParams } from "../interfaces";
import { ToolTips } from "../../constants";
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
import { StepRadio } from "../step_ui/step_radio";
import { t } from "../../i18next_wrapper";
import { AxisStepRadio } from "../step_ui/step_radio";
import { Zero } from "farmbot";
export function TileSetZero(props: StepParams) {
const { dispatch, currentStep, index, currentSequence } = props;
@ -18,9 +19,9 @@ export function TileSetZero(props: StepParams) {
index={index}
confirmStepDeletion={props.confirmStepDeletion} />
<StepContent className={className}>
<StepRadio
<AxisStepRadio
currentSequence={currentSequence}
currentStep={currentStep}
currentStep={currentStep as Zero}
dispatch={dispatch}
index={index}
label={t("Zero")} />

View File

@ -1,14 +1,16 @@
jest.mock("../../../api/crud", () => ({ overwrite: jest.fn() }));
let mockStep = {};
jest.mock("../../../api/crud", () => ({
editStep: jest.fn(x => x.executor(mockStep)),
}));
import * as React from "react";
import { mount } from "enzyme";
import { StepRadio, StepRadioProps } from "../step_radio";
import { AxisStepRadio, AxisStepRadioProps } from "../step_radio";
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
import { FindHome, Calibrate, Zero } from "farmbot";
import { overwrite } from "../../../api/crud";
describe("<StepRadio />", () => {
const currentStep: FindHome = {
const findHomeStep: FindHome = {
kind: "find_home",
args: {
speed: 100,
@ -16,55 +18,55 @@ describe("<StepRadio />", () => {
}
};
const fakeProps = (): StepRadioProps => ({
const fakeProps = (): AxisStepRadioProps => ({
currentSequence: fakeSequence(),
currentStep,
currentStep: findHomeStep,
dispatch: jest.fn(),
index: 0,
label: "Find",
});
it("renders", () => {
const wrapper = mount(<StepRadio {...fakeProps()} />);
const wrapper = mount(<AxisStepRadio {...fakeProps()} />);
expect(wrapper.find("input").length).toEqual(4);
expect(wrapper.text()).toContain("Find");
});
it("handles update for find_home", () => {
const p = fakeProps();
const wrapper = mount(<StepRadio {...p} />);
mockStep = p.currentStep;
const wrapper = mount(<AxisStepRadio {...p} />);
wrapper.find("input").last().simulate("change");
const expectedStep: FindHome = {
kind: "find_home",
args: { speed: 100, axis: "all" }
};
expect(overwrite).toHaveBeenCalledWith(p.currentSequence,
expect.objectContaining({ body: [expectedStep] }));
expect(mockStep).toEqual(expectedStep);
});
it("handles update for calibrate", () => {
const p = fakeProps();
p.currentStep = { kind: "calibrate", args: { axis: "x" } };
const wrapper = mount(<StepRadio {...p} />);
mockStep = p.currentStep;
const wrapper = mount(<AxisStepRadio {...p} />);
wrapper.find("input").last().simulate("change");
const expectedStep: Calibrate = {
kind: "calibrate",
args: { axis: "all" }
};
expect(overwrite).toHaveBeenCalledWith(p.currentSequence,
expect.objectContaining({ body: [expectedStep] }));
expect(mockStep).toEqual(expectedStep);
});
it("handles update for zero", () => {
const p = fakeProps();
p.currentStep = { kind: "zero", args: { axis: "x" } };
const wrapper = mount(<StepRadio {...p} />);
mockStep = p.currentStep;
const wrapper = mount(<AxisStepRadio {...p} />);
wrapper.find("input").last().simulate("change");
const expectedStep: Zero = {
kind: "zero",
args: { axis: "all" }
};
expect(overwrite).toHaveBeenCalledWith(p.currentSequence,
expect.objectContaining({ body: [expectedStep] }));
expect(mockStep).toEqual(expectedStep);
});
});

View File

@ -1,57 +1,32 @@
import * as React from "react";
import { Row, Col } from "../../ui/index";
import {
TaggedSequence, SequenceBodyItem, ALLOWED_AXIS
} from "farmbot";
import { overwrite } from "../../api/crud";
import { defensiveClone } from "../../util";
import { t } from "../../i18next_wrapper";
import {
TaggedSequence, ALLOWED_AXIS, FindHome, Home, Calibrate, Zero
} from "farmbot";
import { editStep } from "../../api/crud";
export interface StepRadioProps {
currentSequence: TaggedSequence;
currentStep: SequenceBodyItem;
dispatch: Function;
index: number;
label: string;
export interface StepRadioProps<T extends string> {
choices: T[];
choiceLabelLookup: Record<T, string>;
currentChoice: T;
onChange(key: T): void;
}
const AXIS_CHOICES: ALLOWED_AXIS[] = ["x", "y", "z", "all"];
export function StepRadio(props: StepRadioProps) {
const isSelected = (choice: ALLOWED_AXIS) => {
if (props.currentStep.kind === "find_home"
|| props.currentStep.kind === "calibrate"
|| props.currentStep.kind === "zero") {
return props.currentStep.args.axis === choice;
}
};
const handleUpdate = (choice: ALLOWED_AXIS) => {
const update = defensiveClone(props.currentStep);
if (update.kind === "find_home"
|| update.kind === "calibrate"
|| update.kind === "zero") {
const nextSequence = defensiveClone(props.currentSequence).body;
update.args.axis = choice;
(nextSequence.body || [])[props.index] = update;
props.dispatch(overwrite(props.currentSequence, nextSequence));
}
};
return <Row>
export const StepRadio = <T extends string>(props: StepRadioProps<T>) =>
<Row>
<Col xs={12}>
<div className="bottom-content">
<div className="channel-fields">
<form>
{AXIS_CHOICES.map((choice, i) =>
{props.choices.map((choice, i) =>
<div key={i} style={{ display: "inline" }}>
<label>
<input type="radio"
value={choice}
onChange={e =>
handleUpdate(e.currentTarget.value as typeof choice)}
checked={isSelected(choice)} />
{` ${t(props.label)} ${choice}`}
onChange={() => props.onChange(choice)}
checked={props.currentChoice === choice} />
{t(props.choiceLabelLookup[choice])}
</label>
</div>)}
</form>
@ -59,4 +34,37 @@ export function StepRadio(props: StepRadioProps) {
</div>
</Col>
</Row>;
type AxisStep = FindHome | Home | Calibrate | Zero;
export interface AxisStepRadioProps {
currentSequence: TaggedSequence;
currentStep: AxisStep;
dispatch: Function;
index: number;
label: string;
}
export const AxisStepRadio = (props: AxisStepRadioProps) => {
const AXIS_CHOICES: ALLOWED_AXIS[] = ["x", "y", "z", "all"];
const CHOICE_LABELS = AXIS_CHOICES.reduce((acc, axis) => {
acc[axis] = `${t(props.label)} ${axis}`;
return acc;
}, {} as Record<ALLOWED_AXIS, string>);
const handleUpdate = (axis: ALLOWED_AXIS) => {
const { currentStep, index, currentSequence } = props;
props.dispatch(editStep({
step: currentStep,
index,
sequence: currentSequence,
executor: (step: AxisStep) => step.args.axis = axis,
}));
};
return <StepRadio
choices={AXIS_CHOICES}
choiceLabelLookup={CHOICE_LABELS}
currentChoice={props.currentStep.args.axis}
onChange={handleUpdate} />;
};