UI removals/reshuffles

pull/1226/head
gabrielburnworth 2019-06-07 17:26:12 -07:00
parent 46d706385e
commit 5719818f60
26 changed files with 243 additions and 205 deletions

View File

@ -1,17 +1,18 @@
const mockDevice = {
checkUpdates: jest.fn(() => { return Promise.resolve(); }),
powerOff: jest.fn(() => { return Promise.resolve(); }),
checkUpdates: jest.fn(() => Promise.resolve()),
powerOff: jest.fn(() => Promise.resolve()),
resetOS: jest.fn(),
reboot: jest.fn(() => { return Promise.resolve(); }),
rebootFirmware: jest.fn(() => { return Promise.resolve(); }),
checkArduinoUpdates: jest.fn(() => { return Promise.resolve(); }),
emergencyLock: jest.fn(() => { return Promise.resolve(); }),
emergencyUnlock: jest.fn(() => { return Promise.resolve(); }),
execSequence: jest.fn(() => { return Promise.resolve(); }),
resetMCU: jest.fn(() => { return Promise.resolve(); }),
togglePin: jest.fn(() => { return Promise.resolve(); }),
home: jest.fn(() => { return Promise.resolve(); }),
sync: jest.fn(() => { return Promise.resolve(); }),
reboot: jest.fn(() => Promise.resolve()),
rebootFirmware: jest.fn(() => Promise.resolve()),
flashFirmware: jest.fn(() => Promise.resolve()),
checkArduinoUpdates: jest.fn(() => Promise.resolve()),
emergencyLock: jest.fn(() => Promise.resolve()),
emergencyUnlock: jest.fn(() => Promise.resolve()),
execSequence: jest.fn(() => Promise.resolve()),
resetMCU: jest.fn(() => Promise.resolve()),
togglePin: jest.fn(() => Promise.resolve()),
home: jest.fn(() => Promise.resolve()),
sync: jest.fn(() => Promise.resolve()),
readStatus: jest.fn(() => Promise.resolve()),
dumpInfo: jest.fn(() => Promise.resolve()),
};
@ -86,6 +87,14 @@ describe("restartFirmware()", function () {
});
});
describe("flashFirmware()", function () {
it("calls flashFirmware", async () => {
await actions.flashFirmware("arduino");
expect(mockDevice.flashFirmware).toHaveBeenCalled();
expect(success).toHaveBeenCalled();
});
});
describe("emergencyLock() / emergencyUnlock", function () {
it("calls emergencyLock", () => {
actions.emergencyLock();

View File

@ -1,6 +1,11 @@
let mockReleaseNoteData = {};
jest.mock("axios", () => ({
get: jest.fn(() => { return Promise.resolve(mockReleaseNoteData); })
get: jest.fn(() => Promise.resolve(mockReleaseNoteData))
}));
jest.mock("../../../api/crud", () => ({
edit: jest.fn(),
save: jest.fn(),
}));
import * as React from "react";
@ -10,39 +15,36 @@ import { bot } from "../../../__test_support__/fake_state/bot";
import { fakeResource } from "../../../__test_support__/fake_resource";
import { FarmbotOsProps } from "../../interfaces";
import axios from "axios";
import { Actions } from "../../../constants";
import { SpecialStatus } from "farmbot";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
import { SaveBtn } from "../../../ui";
import { save, edit } from "../../../api/crud";
describe("<FarmbotOsSettings/>", () => {
beforeEach(() => {
window.alert = jest.fn();
});
const fakeProps = (): FarmbotOsProps => {
return {
account: fakeResource("Device", { id: 0, name: "", tz_offset_hrs: 0 }),
diagnostics: [],
dispatch: jest.fn(),
bot,
botToMqttLastSeen: "",
botToMqttStatus: "up",
sourceFbosConfig: (x) => {
return { value: bot.hardware.configuration[x], consistent: true };
},
shouldDisplay: jest.fn(),
isValidFbosConfig: false,
env: {},
saveFarmwareEnv: jest.fn(),
timeSettings: fakeTimeSettings(),
};
};
const fakeProps = (): FarmbotOsProps => ({
deviceAccount: fakeResource("Device", { id: 0, name: "", tz_offset_hrs: 0 }),
diagnostics: [],
dispatch: jest.fn(),
bot,
botToMqttLastSeen: "",
botToMqttStatus: "up",
sourceFbosConfig: x =>
({ value: bot.hardware.configuration[x], consistent: true }),
shouldDisplay: jest.fn(),
isValidFbosConfig: false,
env: {},
saveFarmwareEnv: jest.fn(),
timeSettings: fakeTimeSettings(),
});
it("renders settings", () => {
const osSettings = mount(<FarmbotOsSettings {...fakeProps()} />);
expect(osSettings.find("input").length).toBe(1);
expect(osSettings.find("button").length).toBe(7);
["NAME", "TIME ZONE", "LAST SEEN", "FARMBOT OS", "CAMERA", "FIRMWARE"]
["NAME", "TIME ZONE", "FARMBOT OS", "CAMERA", "FIRMWARE"]
.map(string => expect(osSettings.text()).toContain(string));
});
@ -70,17 +72,17 @@ describe("<FarmbotOsSettings/>", () => {
it("changes bot name", () => {
const p = fakeProps();
const newName = "new bot name";
const osSettings = shallow(<FarmbotOsSettings {...p} />);
osSettings.find("input")
.simulate("change", { currentTarget: { value: "new bot name" } });
expect(p.dispatch).toHaveBeenCalledWith({
payload: {
specialStatus: SpecialStatus.DIRTY,
update: { name: "new bot name" },
uuid: expect.stringContaining("Device")
},
type: Actions.EDIT_RESOURCE
});
.simulate("change", { currentTarget: { value: newName } });
expect(edit).toHaveBeenCalledWith(p.deviceAccount, { name: newName });
});
it("saves device", () => {
const p = fakeProps();
const wrapper = shallow<FarmbotOsSettings>(<FarmbotOsSettings {...p} />);
wrapper.find(SaveBtn).simulate("click");
expect(save).toHaveBeenCalledWith(p.deviceAccount.uuid);
});
});

View File

@ -1,12 +1,11 @@
import * as React from "react";
import { FarmbotOsProps, FarmbotOsState } from "../interfaces";
import { Widget, WidgetHeader, WidgetBody, Row, Col, SaveBtn } from "../../ui";
import { save, edit, refresh } from "../../api/crud";
import { save, edit } from "../../api/crud";
import { MustBeOnline, isBotOnline } from "../must_be_online";
import { ToolTips, Content } from "../../constants";
import { TimezoneSelector } from "../timezones/timezone_selector";
import { timezoneMismatch } from "../timezones/guess_timezone";
import { LastSeen } from "./fbos_settings/last_seen_row";
import { CameraSelection } from "./fbos_settings/camera_selection";
import { BoardType } from "./fbos_settings/board_type";
import { FarmbotOsRow } from "./fbos_settings/farmbot_os_row";
@ -53,23 +52,23 @@ export class FarmbotOsSettings
}
changeBot = (e: React.ChangeEvent<HTMLInputElement>) => {
const { account, dispatch } = this.props;
dispatch(edit(account, { name: e.currentTarget.value }));
const { deviceAccount, dispatch } = this.props;
dispatch(edit(deviceAccount, { name: e.currentTarget.value }));
}
updateBot = () => {
const { account, dispatch } = this.props;
dispatch(save(account.uuid));
const { deviceAccount, dispatch } = this.props;
dispatch(save(deviceAccount.uuid));
}
handleTimezone = (timezone: string) => {
const { account, dispatch } = this.props;
dispatch(edit(account, { timezone }));
dispatch(save(account.uuid));
const { deviceAccount, dispatch } = this.props;
dispatch(edit(deviceAccount, { timezone }));
dispatch(save(deviceAccount.uuid));
}
maybeWarnTz = () => {
const wrongTZ = timezoneMismatch(this.props.account.body.timezone);
const wrongTZ = timezoneMismatch(this.props.deviceAccount.body.timezone);
if (wrongTZ) {
return t(Content.DIFFERENT_TZ_WARNING);
} else {
@ -77,23 +76,15 @@ export class FarmbotOsSettings
}
}
lastSeen = () => {
return <LastSeen
onClick={() => this.props.dispatch(refresh(this.props.account))}
botToMqttLastSeen={this.props.botToMqttLastSeen}
timeSettings={this.props.timeSettings}
device={this.props.account} />;
}
render() {
const { bot, account, sourceFbosConfig, botToMqttStatus } = this.props;
const { bot, deviceAccount, sourceFbosConfig, botToMqttStatus } = this.props;
const { sync_status } = bot.hardware.informational_settings;
const botOnline = isBotOnline(sync_status, botToMqttStatus);
return <Widget className="device-widget">
<form onSubmit={(e) => e.preventDefault()}>
<WidgetHeader title="Device" helpText={ToolTips.OS_SETTINGS}>
<SaveBtn
status={account.specialStatus}
status={deviceAccount.specialStatus}
onClick={this.updateBot} />
</WidgetHeader>
<WidgetBody>
@ -106,7 +97,7 @@ export class FarmbotOsSettings
<Col xs={9}>
<input name="name"
onChange={this.changeBot}
value={this.props.account.body.name} />
value={this.props.deviceAccount.body.name} />
</Col>
</Row>
<Row>
@ -121,12 +112,11 @@ export class FarmbotOsSettings
</div>
<div>
<TimezoneSelector
currentTimezone={this.props.account.body.timezone}
currentTimezone={this.props.deviceAccount.body.timezone}
onUpdate={this.handleTimezone} />
</div>
</Col>
</Row>
<this.lastSeen />
<MustBeOnline
syncStatus={sync_status}
networkState={this.props.botToMqttStatus}
@ -139,7 +129,10 @@ export class FarmbotOsSettings
dispatch={this.props.dispatch}
sourceFbosConfig={sourceFbosConfig}
shouldDisplay={this.props.shouldDisplay}
botOnline={botOnline} />
botOnline={botOnline}
botToMqttLastSeen={this.props.botToMqttLastSeen}
timeSettings={this.props.timeSettings}
deviceAccount={this.props.deviceAccount} />
<AutoUpdateRow
dispatch={this.props.dispatch}
sourceFbosConfig={sourceFbosConfig} />

View File

@ -4,21 +4,24 @@ import { mount } from "enzyme";
import { bot } from "../../../../__test_support__/fake_state/bot";
import { FarmbotOsRowProps } from "../interfaces";
import { fakeState } from "../../../../__test_support__/fake_state";
import { fakeTimeSettings } from "../../../../__test_support__/fake_time_settings";
import { fakeDevice } from "../../../../__test_support__/resource_index_builder";
describe("<FarmbotOsRow/>", () => {
const fakeProps = (): FarmbotOsRowProps => {
return {
bot,
osReleaseNotesHeading: "",
osReleaseNotes: "",
dispatch: jest.fn(x => x(jest.fn(), fakeState)),
sourceFbosConfig: (x) => {
return { value: bot.hardware.configuration[x], consistent: true };
},
shouldDisplay: () => false,
botOnline: false
};
};
const fakeProps = (): FarmbotOsRowProps => ({
bot,
osReleaseNotesHeading: "",
osReleaseNotes: "",
dispatch: jest.fn(x => x(jest.fn(), fakeState)),
sourceFbosConfig: (x) => {
return { value: bot.hardware.configuration[x], consistent: true };
},
shouldDisplay: () => false,
botOnline: false,
botToMqttLastSeen: "",
deviceAccount: fakeDevice(),
timeSettings: fakeTimeSettings(),
});
it("renders", () => {
const wrapper = mount(<FarmbotOsRow {...fakeProps()} />);

View File

@ -13,9 +13,10 @@ import { FbosDetailsProps } from "../interfaces";
import { fakeFbosConfig } from "../../../../__test_support__/fake_state/resources";
import { fakeState } from "../../../../__test_support__/fake_state";
import {
buildResourceIndex
buildResourceIndex, fakeDevice
} from "../../../../__test_support__/resource_index_builder";
import { edit, save } from "../../../../api/crud";
import { fakeTimeSettings } from "../../../../__test_support__/fake_time_settings";
describe("<FbosDetails/>", () => {
const fakeConfig = fakeFbosConfig();
@ -27,6 +28,9 @@ describe("<FbosDetails/>", () => {
dispatch: jest.fn(x => x(jest.fn(), () => state)),
sourceFbosConfig: () => ({ value: true, consistent: true }),
shouldDisplay: () => false,
botToMqttLastSeen: "",
deviceAccount: fakeDevice(),
timeSettings: fakeTimeSettings(),
});
it("renders", () => {

View File

@ -1,11 +1,14 @@
jest.mock("../../../../api/crud", () => ({ refresh: jest.fn() }));
import * as React from "react";
import { fakeResource } from "../../../../__test_support__/fake_resource";
import { LastSeen, LastSeenProps } from "../last_seen_row";
import { mount } from "enzyme";
import { SpecialStatus, TaggedDevice } from "farmbot";
import { fakeTimeSettings } from "../../../../__test_support__/fake_time_settings";
import { refresh } from "../../../../api/crud";
describe("<LastSeen/>", () => {
describe("<LastSeen />", () => {
const resource = (): TaggedDevice => fakeResource("Device", {
id: 1,
name: "foo",
@ -16,7 +19,7 @@ describe("<LastSeen/>", () => {
const props = (): LastSeenProps => ({
device: resource(),
botToMqttLastSeen: "",
onClick: jest.fn(),
dispatch: jest.fn(),
timeSettings: fakeTimeSettings(),
});
@ -32,7 +35,7 @@ describe("<LastSeen/>", () => {
expect(wrapper.text()).toContain("network connectivity issue");
});
it("tells you when the device was last seen, latest: API", () => {
it("tells you when the device was last seen, no MQTT", () => {
const p = props();
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
p.botToMqttLastSeen = "";
@ -40,6 +43,14 @@ describe("<LastSeen/>", () => {
expect(wrapper.instance().lastSeen).toEqual("2017-08-07T19:40:01.487Z");
});
it("tells you when the device was last seen, latest: API", () => {
const p = props();
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
p.botToMqttLastSeen = "2016-08-07T19:40:01.487Z";
const wrapper = mount<LastSeen>(<LastSeen {...p} />);
expect(wrapper.instance().lastSeen).toEqual("2017-08-07T19:40:01.487Z");
});
it("tells you when the device was last seen, latest: message broker", () => {
const p = props();
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
@ -52,6 +63,6 @@ describe("<LastSeen/>", () => {
const p = props();
const wrapper = mount(<LastSeen {...p} />);
wrapper.find("i").simulate("click");
expect(p.onClick).toHaveBeenCalled();
expect(refresh).toHaveBeenCalled();
});
});

View File

@ -1,6 +1,5 @@
import * as React from "react";
import { Row, Col, Markdown } from "../../../ui/index";
import { OsUpdateButton } from "./os_update_button";
import { Popover, Position } from "@blueprintjs/core";
import { ColWidth } from "../farmbot_os_settings";
@ -35,7 +34,10 @@ export function FarmbotOsRow(props: FarmbotOsRowProps) {
botInfoSettings={bot.hardware.informational_settings}
dispatch={dispatch}
shouldDisplay={props.shouldDisplay}
sourceFbosConfig={sourceFbosConfig} />
sourceFbosConfig={sourceFbosConfig}
botToMqttLastSeen={props.botToMqttLastSeen}
timeSettings={props.timeSettings}
deviceAccount={props.deviceAccount} />
</Popover>
</Col>
<Col xs={3}>

View File

@ -8,6 +8,7 @@ import { FbosDetailsProps } from "./interfaces";
import { SourceFbosConfig, ShouldDisplay, Feature } from "../../interfaces";
import { ConfigurationName } from "farmbot";
import { t } from "../../../i18next_wrapper";
import { LastSeen } from "./last_seen_row";
/** Return an indicator color for the given temperature (C). */
export const colorFromTemp = (temp: number | undefined): string => {
@ -201,6 +202,11 @@ export function FbosDetails(props: FbosDetailsProps) {
} = props.botInfoSettings;
return <div>
<LastSeen
dispatch={props.dispatch}
botToMqttLastSeen={props.botToMqttLastSeen}
timeSettings={props.timeSettings}
device={props.deviceAccount} />
<p><b>Environment: </b>{env}</p>
<CommitDisplay title={"Commit"} repo={"farmbot_os"} commit={commit} />
<p><b>Target: </b>{target}</p>

View File

@ -2,7 +2,7 @@ import {
SourceFbosConfig, BotState, ControlPanelState, ShouldDisplay,
SaveFarmwareEnv, UserEnv
} from "../../interfaces";
import { InformationalSettings } from "farmbot";
import { InformationalSettings, TaggedDevice } from "farmbot";
import { TimeSettings } from "../../../interfaces";
export interface AutoSyncRowProps {
@ -58,6 +58,9 @@ export interface FarmbotOsRowProps {
sourceFbosConfig: SourceFbosConfig;
shouldDisplay: ShouldDisplay;
botOnline: boolean;
botToMqttLastSeen: string;
timeSettings: TimeSettings;
deviceAccount: TaggedDevice;
}
export interface FbosDetailsProps {
@ -65,6 +68,9 @@ export interface FbosDetailsProps {
dispatch: Function;
shouldDisplay: ShouldDisplay;
sourceFbosConfig: SourceFbosConfig;
botToMqttLastSeen: string;
timeSettings: TimeSettings;
deviceAccount: TaggedDevice;
}
export interface OsUpdateButtonProps {

View File

@ -1,15 +1,14 @@
import * as React from "react";
import { Row, Col } from "../../../ui/index";
import moment from "moment";
import { TaggedDevice } from "farmbot";
import { ColWidth } from "../farmbot_os_settings";
import { Content } from "../../../constants";
import { t } from "../../../i18next_wrapper";
import { TimeSettings } from "../../../interfaces";
import { timeFormatString } from "../../../util";
import { refresh } from "../../../api/crud";
export interface LastSeenProps {
onClick?(): void;
dispatch: Function;
botToMqttLastSeen: string;
device: TaggedDevice;
timeSettings: TimeSettings;
@ -53,21 +52,14 @@ export class LastSeen extends React.Component<LastSeenProps, {}> {
}
}
click = () => this.props.dispatch(refresh(this.props.device));
render() {
return <div className="last-seen-row">
<Row>
<Col xs={ColWidth.label}>
<label>
{t("LAST SEEN")}
</label>
</Col>
<Col xs={ColWidth.description}>
<p>
<i className="fa fa-refresh" onClick={this.props.onClick}></i>
{this.show()}
</p>
</Col>
</Row>
<p>
<i className="fa fa-refresh" onClick={this.click}></i>
{this.show()}
</p>
</div>;
}
}

View File

@ -26,7 +26,7 @@ export class Devices extends React.Component<Props, {}> {
<Col xs={12} sm={6}>
<FarmbotOsSettings
diagnostics={selectAllDiagnosticDumps(this.props.resources)}
account={this.props.deviceAccount}
deviceAccount={this.props.deviceAccount}
dispatch={this.props.dispatch}
bot={this.props.bot}
timeSettings={this.props.timeSettings}

View File

@ -170,7 +170,7 @@ export type UserEnv = Record<string, string | undefined>;
export interface FarmbotOsProps {
bot: BotState;
diagnostics: TaggedDiagnosticDump[];
account: TaggedDevice;
deviceAccount: TaggedDevice;
botToMqttStatus: NetworkState;
botToMqttLastSeen: string;
dispatch: Function;

View File

@ -58,11 +58,13 @@ describe("closePlantInfo()", () => {
});
it("plant edit open", () => {
mockPath = "/app/designer/plants/1/edit";
mockPath = "/app/designer/plants/1";
const dispatch = jest.fn();
closePlantInfo(dispatch)();
expect(history.push).not.toHaveBeenCalled();
expect(dispatch).not.toHaveBeenCalled();
expect(history.push).toHaveBeenCalledWith("/app/designer/plants");
expect(dispatch).toHaveBeenCalledWith({
payload: undefined, type: Actions.SELECT_PLANT
});
});
it("plant info open", () => {

View File

@ -1,7 +1,7 @@
import { MovePlantProps, DraggableEvent } from "./interfaces";
import { defensiveClone } from "../util";
import { edit } from "../api/crud";
import { history, getPathArray } from "../history";
import { history } from "../history";
import { Actions } from "../constants";
import { svgToUrl, DEFAULT_ICON } from "../open_farm/icons";
import { getMode } from "./map/util";
@ -28,11 +28,10 @@ export const unselectPlant = (dispatch: Function) => () => {
dispatch({ type: Actions.HOVER_PLANT_LIST_ITEM, payload: undefined });
};
/** Unselect plant and close plant info or select panel if selected and open. */
export const closePlantInfo = (dispatch: Function) => () => {
if (!isNaN(parseInt(getPathArray().slice(-1)[0]))
|| getMode() == Mode.boxSelect) {
// A plant is selected and plant info or select panel is open.
// Unselect and close.
const mode = getMode();
if (mode == Mode.editPlant || mode == Mode.boxSelect) {
unselectPlant(dispatch)();
history.push("/app/designer/plants");
}

View File

@ -149,6 +149,7 @@ export class FarmDesigner extends React.Component<Props, Partial<State>> {
showFarmbot={show_farmbot}
showImages={show_images}
showSensorReadings={show_sensor_readings}
hasSensorReadings={this.props.sensorReadings.length > 0}
dispatch={this.props.dispatch}
timeSettings={this.props.timeSettings}
getConfigValue={this.props.getConfigValue}

View File

@ -327,6 +327,10 @@ describe("getMode()", () => {
expect(getMode()).toEqual(Mode.editPlant);
mockPath = "/app/designer/saved_gardens/templates/1/edit";
expect(getMode()).toEqual(Mode.editPlant);
mockPath = "/app/designer/plants/1";
expect(getMode()).toEqual(Mode.editPlant);
mockPath = "/app/designer/saved_gardens/templates/1";
expect(getMode()).toEqual(Mode.editPlant);
mockPath = "/app/designer/plants/select";
expect(getMode()).toEqual(Mode.boxSelect);
mockPath = "/app/designer/plants/crop_search/mint";

View File

@ -40,6 +40,7 @@ export interface GardenMapLegendProps {
showFarmbot: boolean;
showImages: boolean;
showSensorReadings: boolean;
hasSensorReadings: boolean;
dispatch: Function;
timeSettings: TimeSettings;
getConfigValue: GetWebAppConfigValue;

View File

@ -43,6 +43,7 @@ describe("<GardenMapLegend />", () => {
showFarmbot: false,
showImages: false,
showSensorReadings: false,
hasSensorReadings: false,
dispatch: jest.fn(),
timeSettings: fakeTimeSettings(),
getConfigValue: jest.fn(),
@ -59,7 +60,9 @@ describe("<GardenMapLegend />", () => {
it("shows submenu", () => {
mockDev = true;
const wrapper = mount(<GardenMapLegend {...fakeProps()} />);
const p = fakeProps();
p.hasSensorReadings = true;
const wrapper = mount(<GardenMapLegend {...p} />);
expect(wrapper.html()).toContain("filter");
expect(wrapper.html()).toContain("extras");
mockDev = false;

View File

@ -112,7 +112,7 @@ const LayerToggles = (props: GardenMapLegendProps) => {
dispatch={props.dispatch}
getConfigValue={getConfigValue}
imageAgeInfo={props.imageAgeInfo} />} />
{DevSettings.futureFeaturesEnabled() &&
{DevSettings.futureFeaturesEnabled() && props.hasSensorReadings &&
<LayerToggle
value={props.showSensorReadings}
label={t("Readings?")}

View File

@ -290,6 +290,7 @@ export const getMode = (): Mode => {
const pathArray = getPathArray();
if (pathArray) {
if (pathArray[6] === "add") { return Mode.clickToAdd; }
if (!isNaN(parseInt(pathArray.slice(-1)[0]))) { return Mode.editPlant; }
if (pathArray[5] === "edit") { return Mode.editPlant; }
if (pathArray[6] === "edit") { return Mode.editPlant; }
if (pathArray[4] === "select") { return Mode.boxSelect; }

View File

@ -1,11 +1,7 @@
jest.mock("react-redux", () => ({
connect: jest.fn()
}));
jest.mock("react-redux", () => ({ connect: jest.fn() }));
jest.mock("../../../history", () => ({
history: {
push: jest.fn(),
},
history: { push: jest.fn() },
getPathArray: () => []
}));
@ -17,15 +13,13 @@ import { EditPlantInfoProps } from "../../interfaces";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
describe("<EditPlantInfo />", () => {
const fakeProps = (): EditPlantInfoProps => {
return {
push: jest.fn(),
dispatch: jest.fn(),
findPlant: fakePlant,
openedSavedGarden: undefined,
timeSettings: fakeTimeSettings(),
};
};
const fakeProps = (): EditPlantInfoProps => ({
push: jest.fn(),
dispatch: jest.fn(),
findPlant: fakePlant,
openedSavedGarden: undefined,
timeSettings: fakeTimeSettings(),
});
it("renders", async () => {
const wrapper = mount(<EditPlantInfo {...fakeProps()} />);
@ -33,7 +27,7 @@ describe("<EditPlantInfo />", () => {
expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
const buttons = wrapper.find("button");
expect(buttons.at(1).text()).toEqual("Move FarmBot to this plant");
expect(buttons.at(1).props().hidden).toBeTruthy();
expect(buttons.at(1).props().hidden).toBeFalsy();
});
it("deletes plant", async () => {

View File

@ -1,6 +1,4 @@
jest.mock("react-redux", () => ({
connect: jest.fn()
}));
jest.mock("react-redux", () => ({ connect: jest.fn() }));
jest.mock("../../../history", () => ({
getPathArray: jest.fn(() => { return []; }),
@ -16,23 +14,21 @@ import { history } from "../../../history";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
describe("<PlantInfo />", () => {
function fakeProps(): EditPlantInfoProps {
return {
push: jest.fn(),
findPlant: fakePlant,
dispatch: jest.fn(),
openedSavedGarden: undefined,
timeSettings: fakeTimeSettings(),
};
}
const fakeProps = (): EditPlantInfoProps => ({
push: jest.fn(),
findPlant: fakePlant,
dispatch: jest.fn(),
openedSavedGarden: undefined,
timeSettings: fakeTimeSettings(),
});
it("renders", () => {
const wrapper = mount(<PlantInfo {...fakeProps()} />);
["Strawberry Plant 1", "Plant Type", "Strawberry"].map(string =>
expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
const buttons = wrapper.find("button");
expect(buttons.first().text()).toEqual("Move FarmBot to this plant");
expect(buttons.first().props().hidden).toBeFalsy();
expect(buttons.at(1).text()).toEqual("Move FarmBot to this plant");
expect(buttons.at(1).props().hidden).toBeFalsy();
});
it("renders: no plant", () => {
@ -50,12 +46,4 @@ describe("<PlantInfo />", () => {
expect(wrapper.find("Link").first().props().to)
.toContain("/app/designer/plants");
});
it("has link to plant templates", () => {
const p = fakeProps();
p.openedSavedGarden = "savedGardenUuid";
const wrapper = mount(<PlantInfo {...p} />);
expect(wrapper.find("Link").first().props().to)
.toContain("/app/designer/saved_gardens/templates");
});
});

View File

@ -27,16 +27,14 @@ describe("<PlantPanel/>", () => {
plantStatus: "planned",
};
const fakeProps = (): PlantPanelProps => {
return {
info,
onDestroy: jest.fn(),
updatePlant: jest.fn(),
dispatch: jest.fn(),
inSavedGarden: false,
timeSettings: fakeTimeSettings(),
};
};
const fakeProps = (): PlantPanelProps => ({
info,
onDestroy: jest.fn(),
updatePlant: jest.fn(),
dispatch: jest.fn(),
inSavedGarden: false,
timeSettings: fakeTimeSettings(),
});
it("renders: editing", () => {
const p = fakeProps();
@ -51,8 +49,8 @@ describe("<PlantPanel/>", () => {
it("calls destroy", () => {
const p = fakeProps();
const wrapper = shallow(<PlantPanel {...p} />);
clickButton(wrapper, 0, "Delete");
const wrapper = mount(<PlantPanel {...p} />);
clickButton(wrapper, 2, "Delete");
expect(p.onDestroy).toHaveBeenCalledWith("Plant.0.0");
});

View File

@ -1,11 +1,9 @@
import * as React from "react";
import { connect } from "react-redux";
import { mapStateToProps, formatPlantInfo } from "./map_state_to_props";
import { PlantInfoBase } from "./plant_info_base";
import { PlantPanel } from "./plant_panel";
import { unselectPlant } from "../actions";
import { Link } from "../../link";
import { TaggedPlant } from "../map/interfaces";
import { DesignerPanel, DesignerPanelHeader } from "./designer_panel";
import { t } from "../../i18next_wrapper";
@ -15,25 +13,20 @@ export class PlantInfo extends PlantInfoBase {
default = (plant_info: TaggedPlant) => {
const info = formatPlantInfo(plant_info);
const { name, id } = info;
const plantId = (id || "BROKEN").toString();
return <DesignerPanel panelName={"plant-info"} panelColor={"green"}>
<DesignerPanelHeader
panelName={"plant-info"}
panelColor={"green"}
title={name}
title={`${t("Edit")} ${info.name}`}
backTo={"/app/designer/plants"}
onBack={unselectPlant(this.props.dispatch)}>
<Link
to={`/app/designer/${this.plantCategory}/${plantId}/edit`}
title={t("Edit this plant")}
className="right-button">
{t("Edit")}
</Link>
</DesignerPanelHeader>
<PlantPanel
info={info}
onDestroy={this.destroy}
updatePlant={this.updatePlant}
dispatch={this.props.dispatch}
timeSettings={this.props.timeSettings}
inSavedGarden={!!this.props.openedSavedGarden} />
</DesignerPanel>;
}

View File

@ -124,16 +124,54 @@ const chooseLocation = (to: Record<"x" | "y", number | undefined>) =>
return Promise.resolve();
};
const MoveToPlant =
(props: { x: number, y: number, dispatch: Function, isEditing: boolean }) =>
<button className="fb-button gray"
hidden={props.isEditing}
onClick={() => props.dispatch(chooseLocation({ x: props.x, y: props.y }))
.then(() => history.push("/app/designer/move_to"))}>
{t("Move FarmBot to this plant")}
</button>;
interface MoveToPlantProps {
x: number;
y: number;
dispatch: Function;
hidden: boolean;
}
const ListItem = (props: { name: string, children: React.ReactChild }) =>
const MoveToPlant = (props: MoveToPlantProps) =>
<button className="fb-button gray"
hidden={props.hidden}
onClick={() => props.dispatch(chooseLocation({ x: props.x, y: props.y }))
.then(() => history.push("/app/designer/move_to"))}>
{t("Move FarmBot to this plant")}
</button>;
interface DeleteButtonsProps {
hidden: boolean;
destroy(): void;
}
const DeleteButtons = (props: DeleteButtonsProps) =>
<div>
<div>
<label hidden={props.hidden}>
{t("Delete this plant")}
</label>
</div>
<button
className="fb-button red"
hidden={props.hidden}
onClick={props.destroy}>
{t("Delete")}
</button>
<button
className="fb-button gray"
style={{ marginRight: "10px" }}
hidden={props.hidden}
onClick={() => history.push("/app/designer/plants/select")} >
{t("Delete multiple")}
</button>
</div>;
interface ListItemProps {
name: string;
children: React.ReactChild;
}
const ListItem = (props: ListItemProps) =>
<li>
<p>
{props.name}
@ -192,24 +230,7 @@ export function PlantPanel(props: PlantPanelProps) {
: t(startCase(plantStatus))}
</ListItem>
</ul>
<MoveToPlant x={x} y={y} dispatch={dispatch} isEditing={isEditing} />
<div>
<label hidden={!isEditing}>
{t("Delete this plant")}
</label>
</div>
<button
className="fb-button red"
hidden={!isEditing}
onClick={destroy}>
{t("Delete")}
</button>
<button
className="fb-button gray"
style={{ marginRight: "10px" }}
hidden={!isEditing}
onClick={() => history.push("/app/designer/plants/select")} >
{t("Delete multiple")}
</button>
<MoveToPlant x={x} y={y} dispatch={dispatch} hidden={false} />
<DeleteButtons destroy={destroy} hidden={!isEditing} />
</DesignerPanelContent>;
}

View File

@ -57,4 +57,9 @@ describe("dropDownName()", () => {
const label = dropDownName("Plant 1", { x: 10, y: 20, z: 30 });
expect(label).toEqual("Plant 1 (10, 20, 30)");
});
it("returns untitled label", () => {
const label = dropDownName("", { x: 10, y: 20, z: 30 });
expect(label).toEqual("Untitled (10, 20, 30)");
});
});