UI removals/reshuffles
parent
46d706385e
commit
5719818f60
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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()} />);
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -40,6 +40,7 @@ export interface GardenMapLegendProps {
|
|||
showFarmbot: boolean;
|
||||
showImages: boolean;
|
||||
showSensorReadings: boolean;
|
||||
hasSensorReadings: boolean;
|
||||
dispatch: Function;
|
||||
timeSettings: TimeSettings;
|
||||
getConfigValue: GetWebAppConfigValue;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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?")}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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)");
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue