Merge branch 'staging' of https://github.com/AscendFB/Farmbot-Web-App into staging

pull/1464/head
AscendFB 2019-09-26 12:46:44 +02:00
commit 58aa3d8606
190 changed files with 624 additions and 577 deletions

View File

@ -130,7 +130,7 @@ module CeleryScriptSettingsBag
defn: [n(:execute), n(:nothing)], defn: [n(:execute), n(:nothing)],
}, },
data_value: { data_value: {
defn: ANY_VAR_TOKENIZED, defn: ANY_VAR_TOKENIZED + [n(:point_group), n(:every_point)],
}, },
default_value: { default_value: {
defn: ANY_VAR_TOKENIZED, defn: ANY_VAR_TOKENIZED,
@ -270,6 +270,9 @@ module CeleryScriptSettingsBag
lua: { lua: {
defn: [v(:string)], defn: [v(:string)],
}, },
every_point_type: {
defn: [e(:PointType)],
},
}.map do |(name, conf)| }.map do |(name, conf)|
blk = conf[:blk] blk = conf[:blk]
defn = conf.fetch(:defn) defn = conf.fetch(:defn)
@ -514,6 +517,18 @@ module CeleryScriptSettingsBag
check_resource_type(n, resource_type, resource_id, Device.current) check_resource_type(n, resource_type, resource_id, Device.current)
end, end,
}, },
point_group: {
args: [:resource_id],
tags: [:data, :list_like],
blk: ->(n) do
resource_id = n.args.fetch(:resource_id).value
check_resource_type(n, "PointGroup", resource_id, Device.current)
end,
},
every_point: {
args: [:every_point_type],
tags: [:data, :list_like],
},
}.map { |(name, list)| Corpus.node(name, **list) } }.map { |(name, list)| Corpus.node(name, **list) }
HASH = Corpus.as_json HASH = Corpus.as_json
@ -535,6 +550,8 @@ module CeleryScriptSettingsBag
# the current_device. # the current_device.
# For convenience, we try to set it here, defaulting to 0 # For convenience, we try to set it here, defaulting to 0
node.args[:resource_id].instance_variable_set("@value", owner.id) node.args[:resource_id].instance_variable_set("@value", owner.id)
when "PointGroup"
no_resource(node, PointGroup, resource_id) unless PointGroup.exists?(resource_id)
when *ALLOWED_RESOURCE_TYPE.without("Device") when *ALLOWED_RESOURCE_TYPE.without("Device")
klass = Kernel.const_get(resource_type) klass = Kernel.const_get(resource_type)
resource_ok = klass.exists?(resource_id) resource_ok = klass.exists?(resource_id)

View File

@ -10,6 +10,8 @@ module Devices
string :name string :name
string :timezone string :timezone
time :last_saw_mq time :last_saw_mq
time :last_ota
time :last_ota_checkup
integer :mounted_tool_id, nils: true integer :mounted_tool_id, nils: true
end end

View File

@ -2,5 +2,5 @@ class DeviceSerializer < ApplicationSerializer
attributes :fbos_version, :last_saw_api, :last_saw_mq, attributes :fbos_version, :last_saw_api, :last_saw_mq,
:mounted_tool_id, :name, :serial_number, :mounted_tool_id, :name, :serial_number,
:throttled_at, :throttled_until, :timezone, :throttled_at, :throttled_until, :timezone,
:tz_offset_hrs :tz_offset_hrs, :last_ota, :last_ota_checkup
end end

View File

@ -0,0 +1,7 @@
class RenameDeviceLastOtaChecktoLastOtaCheckup < ActiveRecord::Migration[5.2]
safety_assured
def change
rename_column :devices, :last_ota_check, :last_ota_checkup
end
end

View File

@ -277,7 +277,7 @@ CREATE TABLE public.devices (
serial_number character varying(32), serial_number character varying(32),
mqtt_rate_limit_email_sent_at timestamp without time zone, mqtt_rate_limit_email_sent_at timestamp without time zone,
last_ota timestamp without time zone, last_ota timestamp without time zone,
last_ota_check timestamp without time zone last_ota_checkup timestamp without time zone
); );
@ -3274,6 +3274,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20190804194135'), ('20190804194135'),
('20190804194154'), ('20190804194154'),
('20190823164837'), ('20190823164837'),
('20190918185359'); ('20190918185359'),
('20190924190539');

View File

@ -28,7 +28,9 @@ import {
TaggedPointGroup, TaggedPointGroup,
} from "farmbot"; } from "farmbot";
import { fakeResource } from "../fake_resource"; import { fakeResource } from "../fake_resource";
import { ExecutableType, PinBindingType } from "farmbot/dist/resources/api_resources"; import {
ExecutableType, PinBindingType
} from "farmbot/dist/resources/api_resources";
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware"; import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
import { MessageType } from "../../sequences/interfaces"; import { MessageType } from "../../sequences/interfaces";

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
let mockPath = ""; let mockPath = "";
jest.mock("../history", () => ({ jest.mock("../history", () => ({
getPathArray: jest.fn(() => mockPath.split("/")), getPathArray: jest.fn(() => mockPath.split("/")),
@ -7,7 +5,7 @@ jest.mock("../history", () => ({
})); }));
import * as React from "react"; import * as React from "react";
import { App, AppProps, mapStateToProps } from "../app"; import { RawApp as App, AppProps, mapStateToProps } from "../app";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { bot } from "../__test_support__/fake_state/bot"; import { bot } from "../__test_support__/fake_state/bot";
import { import {
@ -25,27 +23,25 @@ import { fakePings } from "../__test_support__/fake_state/pings";
const FULLY_LOADED: ResourceName[] = [ const FULLY_LOADED: ResourceName[] = [
"Sequence", "Regimen", "FarmEvent", "Point", "Tool", "Device"]; "Sequence", "Regimen", "FarmEvent", "Point", "Tool", "Device"];
const fakeProps = (): AppProps => { const fakeProps = (): AppProps => ({
return { timeSettings: fakeTimeSettings(),
timeSettings: fakeTimeSettings(), dispatch: jest.fn(),
dispatch: jest.fn(), loaded: [],
loaded: [], logs: [],
logs: [], user: fakeUser(),
user: fakeUser(), bot: bot,
bot: bot, consistent: true,
consistent: true, axisInversion: { x: false, y: false, z: false },
axisInversion: { x: false, y: false, z: false }, firmwareConfig: undefined,
firmwareConfig: undefined, xySwap: false,
xySwap: false, animate: false,
animate: false, getConfigValue: jest.fn(),
getConfigValue: jest.fn(), tour: undefined,
tour: undefined, resources: buildResourceIndex().index,
resources: buildResourceIndex().index, autoSync: false,
autoSync: false, alertCount: 0,
alertCount: 0, pings: fakePings()
pings: fakePings() });
};
};
describe("<App />: Controls Pop-Up", () => { describe("<App />: Controls Pop-Up", () => {
function controlsPopUp(page: string, exists: boolean) { function controlsPopUp(page: string, exists: boolean) {

View File

@ -1,11 +1,10 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../labs/labs_features", () => ({ LabsFeatures: () => <div /> })); jest.mock("../labs/labs_features", () => ({ LabsFeatures: () => <div /> }));
import * as React from "react"; import * as React from "react";
import { fakeState } from "../../__test_support__/fake_state"; import { fakeState } from "../../__test_support__/fake_state";
import { mapStateToProps } from "../state_to_props"; import { mapStateToProps } from "../state_to_props";
import { shallow, mount } from "enzyme"; import { shallow, mount } from "enzyme";
import { Account } from "../index"; import { RawAccount as Account } from "../index";
import { edit } from "../../api/crud"; import { edit } from "../../api/crud";
describe("<Account />", () => { describe("<Account />", () => {

View File

@ -4,12 +4,15 @@ const mock = {
} }
}; };
jest.mock("axios", jest.mock("axios", () => ({
() => ({ post: jest.fn(() => Promise.resolve(mock.response)) })); post: jest.fn(() => Promise.resolve(mock.response))
}));
import { API } from "../../api"; import { API } from "../../api";
import { Content } from "../../constants"; import { Content } from "../../constants";
import { requestAccountExport, generateFilename } from "../request_account_export"; import {
requestAccountExport, generateFilename
} from "../request_account_export";
import { success } from "../../toast/toast"; import { success } from "../../toast/toast";
import axios from "axios"; import axios from "axios";
import { fakeDevice } from "../../__test_support__/resource_index_builder"; import { fakeDevice } from "../../__test_support__/resource_index_builder";
@ -32,7 +35,8 @@ describe("requestAccountExport", () => {
expect(success).toHaveBeenCalledWith(Content.EXPORT_SENT); expect(success).toHaveBeenCalledWith(Content.EXPORT_SENT);
}); });
it("downloads the data synchronously (when API has no email support)", async () => { it("downloads the data synchronously (when API has no email support)", async (
) => {
mock.response.data = {}; mock.response.data = {};
window.URL = window.URL || ({} as typeof window.URL); window.URL = window.URL || ({} as typeof window.URL);
window.URL.createObjectURL = jest.fn(); window.URL.createObjectURL = jest.fn();

View File

@ -18,7 +18,8 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
</label> </label>
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<button onClick={props.onClick} className="green fb-button" type="button" > <button className="green fb-button" type="button"
onClick={props.onClick}>
{t("Export")} {t("Export")}
</button> </button>
</Col> </Col>

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { import {
BlurableInput, Widget, WidgetHeader, WidgetBody, SaveBtn BlurableInput, Widget, WidgetHeader, WidgetBody, SaveBtn
} from "../../ui/index"; } from "../../ui/index";

View File

@ -1,7 +1,6 @@
import { API } from "../api"; import { API } from "../api";
import { Content } from "../constants"; import { Content } from "../constants";
import { success } from "../toast/toast"; import { success } from "../toast/toast";
import axios, { AxiosResponse } from "axios"; import axios, { AxiosResponse } from "axios";
import { DeviceAccountSettings } from "farmbot/dist/resources/api_resources"; import { DeviceAccountSettings } from "farmbot/dist/resources/api_resources";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";

View File

@ -17,7 +17,6 @@ import { ResourceIndex } from "../resources/interfaces";
import { SequenceBodyItem } from "farmbot/dist"; import { SequenceBodyItem } from "farmbot/dist";
import { Actions } from "../constants"; import { Actions } from "../constants";
import { maybeStartTracking } from "./maybe_start_tracking"; import { maybeStartTracking } from "./maybe_start_tracking";
import { newTaggedResource } from "../sync/actions"; import { newTaggedResource } from "../sync/actions";
import { arrayUnwrap } from "../resources/util"; import { arrayUnwrap } from "../resources/util";
import { findByUuid } from "../resources/reducer_support"; import { findByUuid } from "../resources/reducer_support";

View File

@ -21,7 +21,10 @@ jest.mock("../../redux/store", () => {
jest.mock("../auto_sync_handle_inbound", () => ({ handleInbound: jest.fn() })); jest.mock("../auto_sync_handle_inbound", () => ({ handleInbound: jest.fn() }));
import { dispatchNetworkUp, dispatchNetworkDown, dispatchQosStart, networkUptimeThrottleStats } from "../index"; import {
dispatchNetworkUp, dispatchNetworkDown, dispatchQosStart,
networkUptimeThrottleStats,
} from "../index";
import { networkUp, networkDown } from "../actions"; import { networkUp, networkDown } from "../actions";
import { GetState } from "../../redux/interfaces"; import { GetState } from "../../redux/interfaces";
import { autoSync, routeMqttData } from "../auto_sync"; import { autoSync, routeMqttData } from "../auto_sync";

View File

@ -1,12 +1,11 @@
import { GetState } from "../redux/interfaces"; import { GetState } from "../redux/interfaces";
import { maybeDetermineUuid } from "../resources/selectors"; import { maybeDetermineUuid } from "../resources/selectors";
import { import { TaggedResource, SpecialStatus } from "farmbot";
TaggedResource,
SpecialStatus
} from "farmbot";
import { overwrite, init } from "../api/crud"; import { overwrite, init } from "../api/crud";
import { handleInbound } from "./auto_sync_handle_inbound"; import { handleInbound } from "./auto_sync_handle_inbound";
import { SyncPayload, MqttDataResult, Reason, UpdateMqttData } from "./interfaces"; import {
SyncPayload, MqttDataResult, Reason, UpdateMqttData
} from "./interfaces";
import { outstandingRequests } from "./data_consistency"; import { outstandingRequests } from "./data_consistency";
import { newTaggedResource } from "../sync/actions"; import { newTaggedResource } from "../sync/actions";
@ -16,7 +15,8 @@ export function decodeBinary(payload: Buffer): SyncPayload {
const SKIP_THESE = ["DeviceSerialNumber"]; // Only FBOS Cares about this one. const SKIP_THESE = ["DeviceSerialNumber"]; // Only FBOS Cares about this one.
export function routeMqttData(chan: string, payload: Buffer): MqttDataResult<TaggedResource> { export function routeMqttData(chan: string, payload: Buffer):
MqttDataResult<TaggedResource> {
/** Skip irrelevant messages: only resource auto-sync messages are desired. /** Skip irrelevant messages: only resource auto-sync messages are desired.
* eg, `bot/device_#/sync/Resource/#` */ * eg, `bot/device_#/sync/Resource/#` */
if (!(chan.split("/")[2] == "sync")) { return { status: "SKIP" }; } if (!(chan.split("/")[2] == "sync")) { return { status: "SKIP" }; }
@ -37,7 +37,8 @@ export function routeMqttData(chan: string, payload: Buffer): MqttDataResult<Tag
} }
} }
export function asTaggedResource(data: UpdateMqttData<TaggedResource>): TaggedResource { export function asTaggedResource(data: UpdateMqttData<TaggedResource>):
TaggedResource {
return newTaggedResource(data.kind, data.body)[0]; return newTaggedResource(data.kind, data.body)[0];
} }

View File

@ -43,8 +43,9 @@ export const HACKY_FLAGS = {
export const incomingLegacyStatus = (statusMessage: HardwareState) => export const incomingLegacyStatus = (statusMessage: HardwareState) =>
({ type: Actions.LEGACY_BOT_CHANGE, payload: statusMessage }); ({ type: Actions.LEGACY_BOT_CHANGE, payload: statusMessage });
export const incomingStatus = export const incomingStatus = (payload: DeepPartial<HardwareState>) => ({
(payload: DeepPartial<HardwareState>) => ({ type: Actions.STATUS_UPDATE, payload }); type: Actions.STATUS_UPDATE, payload
});
/** Determine if an incoming log has a certain channel. If it is, execute the /** Determine if an incoming log has a certain channel. If it is, execute the
* supplied callback. */ * supplied callback. */

View File

@ -1,8 +1,6 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
import * as React from "react"; import * as React from "react";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { Controls } from "../controls"; import { RawControls as Controls } from "../controls";
import { bot } from "../../__test_support__/fake_state/bot"; import { bot } from "../../__test_support__/fake_state/bot";
import { import {
fakePeripheral, fakeWebcamFeed, fakeSensor fakePeripheral, fakeWebcamFeed, fakeSensor

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { Row, Col } from "../ui/index"; import { Row, Col } from "../ui/index";
import { AxisDisplayGroupProps } from "./interfaces"; import { AxisDisplayGroupProps } from "./interfaces";
import { isNumber } from "lodash"; import { isNumber } from "lodash";

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { AxisInputBox } from "./axis_input_box"; import { AxisInputBox } from "./axis_input_box";
import { Row, Col } from "../ui/index"; import { Row, Col } from "../ui/index";
import { AxisInputBoxGroupProps, AxisInputBoxGroupState } from "./interfaces"; import { AxisInputBoxGroupProps, AxisInputBoxGroupState } from "./interfaces";
import { isNumber } from "lodash"; import { isNumber } from "lodash";
@ -59,7 +58,7 @@ export class AxisInputBoxGroup extends
onClick={this.clicked} onClick={this.clicked}
disabled={this.props.disabled || false} disabled={this.props.disabled || false}
title={t("Move to chosen location")} title={t("Move to chosen location")}
className="full-width green go fb-button" > className="full-width green go fb-button">
{t("GO")} {t("GO")}
</button> </button>
</Col> </Col>

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { Row, Col } from "../../ui"; import { Row, Col } from "../../ui";
import { BotLocationData } from "../../devices/interfaces"; import { BotLocationData } from "../../devices/interfaces";
import { moveAbs } from "../../devices/actions"; import { moveAbs } from "../../devices/actions";

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { McuParams } from "farmbot"; import { McuParams } from "farmbot";
import { BotPosition } from "../../devices/interfaces"; import { BotPosition } from "../../devices/interfaces";
import { changeStepSize } from "../../devices/actions"; import { changeStepSize } from "../../devices/actions";

View File

@ -3,8 +3,9 @@ import { Xyz, LocationName, Dictionary } from "farmbot";
import moment from "moment"; import moment from "moment";
import { BotLocationData, BotPosition } from "../../devices/interfaces"; import { BotLocationData, BotPosition } from "../../devices/interfaces";
import { trim } from "../../util"; import { trim } from "../../util";
import {
import { cloneDeep, max, get, isNumber, isEqual, takeRight, ceil, range } from "lodash"; cloneDeep, max, get, isNumber, isEqual, takeRight, ceil, range
} from "lodash";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";
const HEIGHT = 50; const HEIGHT = 50;

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { BooleanSetting } from "../../session_keys"; import { BooleanSetting } from "../../session_keys";
import { ToggleButton } from "../toggle_button"; import { ToggleButton } from "../toggle_button";
import { ToggleWebAppBool, GetWebAppBool } from "./interfaces"; import { ToggleWebAppBool, GetWebAppBool } from "./interfaces";
@ -7,16 +6,17 @@ import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
import { DevSettings } from "../../account/dev/dev_support"; import { DevSettings } from "../../account/dev/dev_support";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";
export const moveWidgetSetting = (toggle: ToggleWebAppBool, getValue: GetWebAppBool) => export const moveWidgetSetting =
({ label, setting }: { label: string, setting: BooleanConfigKey }) => (toggle: ToggleWebAppBool, getValue: GetWebAppBool) =>
<fieldset> ({ label, setting }: { label: string, setting: BooleanConfigKey }) =>
<label> <fieldset>
{t(label)} <label>
</label> {t(label)}
<ToggleButton </label>
toggleAction={toggle(BooleanSetting[setting])} <ToggleButton
toggleValue={getValue(setting)} /> toggleAction={toggle(BooleanSetting[setting])}
</fieldset>; toggleValue={getValue(setting)} />
</fieldset>;
export const MoveWidgetSettingsMenu = ({ toggle, getValue }: { export const MoveWidgetSettingsMenu = ({ toggle, getValue }: {
toggle: ToggleWebAppBool, toggle: ToggleWebAppBool,

View File

@ -89,4 +89,4 @@ export const DeleteButton = (props: DeleteButtonProps) =>
props.dispatch(destroy(props.uuid)) props.dispatch(destroy(props.uuid))
.then(props.onDestroy || (() => { }))}> .then(props.onDestroy || (() => { }))}>
{props.children || <i className="fa fa-times" />} {props.children || <i className="fa fa-times" />}
</button >; </button>;

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { ToggleButtonProps } from "./interfaces"; import { ToggleButtonProps } from "./interfaces";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";

View File

@ -3,18 +3,26 @@ import { fakeWebcamFeed } from "../../../__test_support__/fake_state/resources";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { Show, IndexIndicator } from "../show"; import { Show, IndexIndicator } from "../show";
import { props } from "../test_helpers"; import { props } from "../test_helpers";
import { PLACEHOLDER_FARMBOT } from "../../../farmware/images/image_flipper";
describe("<Show/>", () => { describe("<Show/>", () => {
const feed1 = fakeWebcamFeed();
const feed2 = fakeWebcamFeed();
const p = props([feed1, feed2]);
it("Renders feed title", () => { it("Renders feed title", () => {
const feed1 = fakeWebcamFeed();
const feed2 = fakeWebcamFeed();
const p = props([feed1, feed2]);
const el = mount(<Show {...p} />); const el = mount(<Show {...p} />);
expect(el.text()).toContain(feed1.body.name); expect(el.text()).toContain(feed1.body.name);
el.find(".image-flipper-right").first().simulate("click"); el.find(".image-flipper-right").first().simulate("click");
el.render(); el.render();
expect(el.text()).toContain(feed2.body.name); expect(el.text()).toContain(feed2.body.name);
}); });
it("returns a PLACEHOLDER_FEED", () => {
const comp = new Show(p);
const result = comp.getMessage("http://geocities.com/" + PLACEHOLDER_FARMBOT);
expect(result).toEqual("Click the edit button to add or edit a feed URL.");
});
}); });
describe("<IndexIndicator/>", () => { describe("<IndexIndicator/>", () => {

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Widget, WidgetHeader, WidgetBody } from "../../ui/index"; import { Widget, WidgetHeader, WidgetBody } from "../../ui/index";
import { ToolTips } from "../../constants"; import { ToolTips } from "../../constants";
import { WebcamPanelProps } from "./interfaces"; import { WebcamPanelProps } from "./interfaces";
import { KeyValEditRow } from "../key_val_edit_row"; import { KeyValEditRow } from "../key_val_edit_row";

View File

@ -5,7 +5,6 @@ import { WebcamPanelProps } from "./interfaces";
import { TaggedWebcamFeed, SpecialStatus } from "farmbot"; import { TaggedWebcamFeed, SpecialStatus } from "farmbot";
import { edit, save, destroy, init } from "../../api/crud"; import { edit, save, destroy, init } from "../../api/crud";
import { error } from "../../toast/toast"; import { error } from "../../toast/toast";
import { WebcamFeed } from "farmbot/dist/resources/api_resources"; import { WebcamFeed } from "farmbot/dist/resources/api_resources";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Widget, WidgetHeader, FallbackImg, WidgetBody } from "../../ui/index"; import { Widget, WidgetHeader, FallbackImg, WidgetBody } from "../../ui/index";
import { ToolTips } from "../../constants"; import { ToolTips } from "../../constants";
import { WebcamPanelProps } from "./interfaces"; import { WebcamPanelProps } from "./interfaces";
import { PLACEHOLDER_FARMBOT } from "../../farmware/images/image_flipper"; import { PLACEHOLDER_FARMBOT } from "../../farmware/images/image_flipper";

View File

@ -1,8 +1,6 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
import * as React from "react"; import * as React from "react";
import { shallow, render } from "enzyme"; import { shallow, render } from "enzyme";
import { Devices } from "../devices"; import { RawDevices as Devices } from "../devices";
import { Props } from "../interfaces"; import { Props } from "../interfaces";
import { auth } from "../../__test_support__/fake_state/token"; import { auth } from "../../__test_support__/fake_state/token";
import { bot } from "../../__test_support__/fake_state/bot"; import { bot } from "../../__test_support__/fake_state/bot";

View File

@ -400,5 +400,6 @@ export function changeStepSize(integer: number) {
} }
export function badVersion() { export function badVersion() {
info(t("You are running an old version of FarmBot OS."), t("Please Update"), "red"); info(t("You are running an old version of FarmBot OS."),
t("Please Update"), "red");
} }

View File

@ -1,4 +1,3 @@
let mockReleaseNoteData = {}; let mockReleaseNoteData = {};
jest.mock("axios", () => ({ jest.mock("axios", () => ({
get: jest.fn(() => Promise.resolve(mockReleaseNoteData)) get: jest.fn(() => Promise.resolve(mockReleaseNoteData))
@ -9,6 +8,10 @@ jest.mock("../../../api/crud", () => ({
save: jest.fn(), save: jest.fn(),
})); }));
jest.mock("../fbos_settings/boot_sequence_selector", () => ({
BootSequenceSelector: () => <div />
}));
import * as React from "react"; import * as React from "react";
import { FarmbotOsSettings } from "../farmbot_os_settings"; import { FarmbotOsSettings } from "../farmbot_os_settings";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
@ -19,14 +22,6 @@ import axios from "axios";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings"; import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
import { edit } from "../../../api/crud"; import { edit } from "../../../api/crud";
jest.mock("react-redux", () => ({
connect: jest.fn(() => {
return () => {
return () => "";
};
})
}));
describe("<FarmbotOsSettings />", () => { describe("<FarmbotOsSettings />", () => {
beforeEach(() => { beforeEach(() => {
window.alert = jest.fn(); window.alert = jest.fn();
@ -87,5 +82,4 @@ describe("<FarmbotOsSettings />", () => {
.simulate("change", { currentTarget: { value: newName } }); .simulate("change", { currentTarget: { value: newName } });
expect(edit).toHaveBeenCalledWith(p.deviceAccount, { name: newName }); expect(edit).toHaveBeenCalledWith(p.deviceAccount, { name: newName });
}); });
}); });

View File

@ -1,6 +1,4 @@
jest.mock("../../actions", () => ({ jest.mock("../../actions", () => ({ settingToggle: jest.fn() }));
settingToggle: jest.fn()
}));
import * as React from "react"; import * as React from "react";
import { PinGuardMCUInputGroup } from "../pin_guard_input_group"; import { PinGuardMCUInputGroup } from "../pin_guard_input_group";
@ -8,7 +6,9 @@ import { mount } from "enzyme";
import { PinGuardMCUInputGroupProps } from "../interfaces"; import { PinGuardMCUInputGroupProps } from "../interfaces";
import { bot } from "../../../__test_support__/fake_state/bot"; import { bot } from "../../../__test_support__/fake_state/bot";
import { settingToggle } from "../../actions"; import { settingToggle } from "../../actions";
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder"; import {
buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
describe("<PinGuardMCUInputGroup/>", () => { describe("<PinGuardMCUInputGroup/>", () => {
const fakeProps = (): PinGuardMCUInputGroupProps => { const fakeProps = (): PinGuardMCUInputGroupProps => {

View File

@ -15,9 +15,10 @@ interface AxisStatus {
* If neither of these are enabled, FarmBot can do some pretty dangerous things, * If neither of these are enabled, FarmBot can do some pretty dangerous things,
* such as smashing tools and ramming into tool bays. * such as smashing tools and ramming into tool bays.
* *
* This function returns a 2 dimensional array describing whether or not a particular * This function returns a 2 dimensional array describing whether or not
* axis has at least one of the precautions in place. Useful for checking if it is safe * a particular axis has at least one of the precautions in place.
* to proceed with certain actions that could damage the bot. * Useful for checking if it is safe to proceed with certain actions that
* could damage the bot.
*/ */
export function axisTrackingStatus(h: McuParams): AxisStatus[] { export function axisTrackingStatus(h: McuParams): AxisStatus[] {
const stats = enabledAxisMap(h); const stats = enabledAxisMap(h);

View File

@ -20,7 +20,9 @@ export class DiagnosticDumpRow extends React.Component<Props, {}> {
render() { render() {
return <Row> return <Row>
<Col xsOffset={3} xs={8}> <Col xsOffset={3} xs={8}>
{t("Report {{ticket}} (Saved {{age}})", { ticket: this.ticket, age: this.age })} {t("Report {{ticket}} (Saved {{age}})", {
ticket: this.ticket, age: this.age
})}
</Col> </Col>
<Col xs={1}> <Col xs={1}>
<button <button
@ -30,6 +32,6 @@ export class DiagnosticDumpRow extends React.Component<Props, {}> {
<i className="fa fa-times" /> <i className="fa fa-times" />
</button> </button>
</Col> </Col>
</Row >; </Row>;
} }
} }

View File

@ -1,4 +1,6 @@
import * as React from "react"; import * as React from "react";
import axios from "axios";
import { t } from "../../i18next_wrapper";
import { FarmbotOsProps, FarmbotOsState } from "../interfaces"; import { FarmbotOsProps, FarmbotOsState } from "../interfaces";
import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../../ui"; import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../../ui";
import { save, edit } from "../../api/crud"; import { save, edit } from "../../api/crud";
@ -14,8 +16,6 @@ import { AutoSyncRow } from "./fbos_settings/auto_sync_row";
import { isUndefined } from "lodash"; import { isUndefined } from "lodash";
import { PowerAndReset } from "./fbos_settings/power_and_reset"; import { PowerAndReset } from "./fbos_settings/power_and_reset";
import { SendDiagnosticReport } from "./send_diagnostic_report"; import { SendDiagnosticReport } from "./send_diagnostic_report";
import axios from "axios";
import { t } from "../../i18next_wrapper";
import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector"; import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector";
export enum ColWidth { export enum ColWidth {
@ -70,11 +70,7 @@ export class FarmbotOsSettings
maybeWarnTz = () => { maybeWarnTz = () => {
const wrongTZ = timezoneMismatch(this.props.deviceAccount.body.timezone); const wrongTZ = timezoneMismatch(this.props.deviceAccount.body.timezone);
if (wrongTZ) { return wrongTZ ? t(Content.DIFFERENT_TZ_WARNING) : "";
return t(Content.DIFFERENT_TZ_WARNING);
} else {
return "";
}
} }
render() { render() {

View File

@ -173,6 +173,36 @@ describe("<FbosDetails/>", () => {
const wrapper = mount(<FbosDetails {...p} />); const wrapper = mount(<FbosDetails {...p} />);
expect(wrapper.text().toLowerCase()).toContain("voltage"); expect(wrapper.text().toLowerCase()).toContain("voltage");
}); });
it("displays cpu usage", () => {
const p = fakeProps();
// tslint:disable-next-line:no-any
(p.botInfoSettings as any).cpu_usage = 10;
const wrapper = mount(<FbosDetails {...p} />);
expect(wrapper.text().toLowerCase()).toContain("cpu usage: 10%");
});
it("displays ip address", () => {
const p = fakeProps();
p.botInfoSettings.private_ip = "192.168.0.100";
const wrapper = mount(<FbosDetails {...p} />);
expect(wrapper.text().toLowerCase()).toContain("ip address");
});
it("displays last OTA check date", () => {
const p = fakeProps();
p.deviceAccount.body.last_ota_checkup = "2018-01-11T20:20:38.362Z";
const wrapper = mount(<FbosDetails {...p} />);
expect(wrapper.text().toLowerCase())
.toContain("last checked for updates: january");
});
it("displays last OTA date", () => {
const p = fakeProps();
p.deviceAccount.body.last_ota = "2018-02-11T20:20:38.362Z";
const wrapper = mount(<FbosDetails {...p} />);
expect(wrapper.text().toLowerCase()).toContain("last updated: february");
});
}); });
describe("betaReleaseOptIn()", () => { describe("betaReleaseOptIn()", () => {

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Row, Col } from "../../../ui/index"; import { Row, Col } from "../../../ui/index";
import { ToggleButton } from "../../../controls/toggle_button"; import { ToggleButton } from "../../../controls/toggle_button";
import { Content } from "../../../constants"; import { Content } from "../../../constants";
import { updateConfig } from "../../actions"; import { updateConfig } from "../../actions";

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Row, Col } from "../../../ui/index"; import { Row, Col } from "../../../ui/index";
import { ColWidth } from "../farmbot_os_settings"; import { ColWidth } from "../farmbot_os_settings";
import { ToggleButton } from "../../../controls/toggle_button"; import { ToggleButton } from "../../../controls/toggle_button";
import { updateConfig } from "../../actions"; import { updateConfig } from "../../actions";

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Row, Col, BlurableInput } from "../../../ui/index"; import { Row, Col, BlurableInput } from "../../../ui/index";
import { success, error } from "../../../toast/toast"; import { success, error } from "../../../toast/toast";
import { getDevice } from "../../../device"; import { getDevice } from "../../../device";
import { transferOwnership } from "../../transfer_ownership/transfer_ownership"; import { transferOwnership } from "../../transfer_ownership/transfer_ownership";

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Row, Col } from "../../../ui/index"; import { Row, Col } from "../../../ui/index";
import { Content } from "../../../constants"; import { Content } from "../../../constants";
import { factoryReset, updateConfig } from "../../actions"; import { factoryReset, updateConfig } from "../../actions";
import { ToggleButton } from "../../../controls/toggle_button"; import { ToggleButton } from "../../../controls/toggle_button";
@ -75,6 +74,6 @@ export function FactoryResetRow(props: FactoryResetRowProps) {
disabled={!!disableFactoryReset.value} disabled={!!disableFactoryReset.value}
sourceFbosConfig={sourceFbosConfig} /> sourceFbosConfig={sourceFbosConfig} />
</Col> </Col>
</Row > </Row>
</div >; </div>;
} }

View File

@ -61,5 +61,5 @@ export function FarmbotOsRow(props: FarmbotOsRowProps) {
shouldDisplay={props.shouldDisplay} shouldDisplay={props.shouldDisplay}
botOnline={botOnline} /> botOnline={botOnline} />
</Col> </Col>
</Row >; </Row>;
} }

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Row, Col } from "../../../ui"; import { Row, Col } from "../../../ui";
import { ColWidth } from "../farmbot_os_settings"; import { ColWidth } from "../farmbot_os_settings";
import { t } from "../../../i18next_wrapper"; import { t } from "../../../i18next_wrapper";

View File

@ -2,7 +2,7 @@ import * as React from "react";
import { Saucer } from "../../../ui/index"; import { Saucer } from "../../../ui/index";
import { ToggleButton } from "../../../controls/toggle_button"; import { ToggleButton } from "../../../controls/toggle_button";
import { updateConfig } from "../../actions"; import { updateConfig } from "../../actions";
import { last, isNumber } from "lodash"; import { last, isNumber, isString } from "lodash";
import { Content } from "../../../constants"; import { Content } from "../../../constants";
import { FbosDetailsProps } from "./interfaces"; import { FbosDetailsProps } from "./interfaces";
import { SourceFbosConfig, ShouldDisplay, Feature } from "../../interfaces"; import { SourceFbosConfig, ShouldDisplay, Feature } from "../../interfaces";
@ -10,6 +10,9 @@ import { ConfigurationName } from "farmbot";
import { t } from "../../../i18next_wrapper"; import { t } from "../../../i18next_wrapper";
import { LastSeen } from "./last_seen_row"; import { LastSeen } from "./last_seen_row";
import { Popover } from "@blueprintjs/core"; import { Popover } from "@blueprintjs/core";
import moment from "moment";
import { timeFormatString } from "../../../util";
import { TimeSettings } from "../../../interfaces";
/** Return an indicator color for the given temperature (C). */ /** Return an indicator color for the given temperature (C). */
export const colorFromTemp = (temp: number | undefined): string => { export const colorFromTemp = (temp: number | undefined): string => {
@ -258,14 +261,21 @@ const BetaReleaseOptInButton = (
</fieldset>; </fieldset>;
}; };
/** Format datetime string for display. */
const reformatDatetime = (datetime: string, timeSettings: TimeSettings) =>
moment(datetime)
.utcOffset(timeSettings.utcOffset)
.format(`MMMM D, ${timeFormatString(timeSettings)}`);
/** Current technical information about FarmBot OS running on the device. */ /** Current technical information about FarmBot OS running on the device. */
export function FbosDetails(props: FbosDetailsProps) { export function FbosDetails(props: FbosDetailsProps) {
const { const {
env, commit, target, node_name, firmware_version, firmware_commit, env, commit, target, node_name, firmware_version, firmware_commit,
soc_temp, wifi_level, uptime, memory_usage, disk_usage, throttled, soc_temp, wifi_level, uptime, memory_usage, disk_usage, throttled,
wifi_level_percent, wifi_level_percent, cpu_usage, private_ip,
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
} = props.botInfoSettings as any; } = props.botInfoSettings as any;
const { last_ota, last_ota_checkup } = props.deviceAccount.body;
return <div> return <div>
<LastSeen <LastSeen
@ -277,6 +287,8 @@ export function FbosDetails(props: FbosDetailsProps) {
<CommitDisplay title={t("Commit")} repo={"farmbot_os"} commit={commit} /> <CommitDisplay title={t("Commit")} repo={"farmbot_os"} commit={commit} />
<p><b>{t("Target")}: </b>{target}</p> <p><b>{t("Target")}: </b>{target}</p>
<p><b>{t("Node name")}: </b>{last((node_name || "").split("@"))}</p> <p><b>{t("Node name")}: </b>{last((node_name || "").split("@"))}</p>
<p><b>{t("Device ID")}: </b>{props.deviceAccount.body.id}</p>
{isString(private_ip) && <p><b>{t("Local IP address")}: </b>{private_ip}</p>}
<p><b>{t("Firmware")}: </b>{firmware_version}</p> <p><b>{t("Firmware")}: </b>{firmware_version}</p>
<CommitDisplay title={t("Firmware commit")} <CommitDisplay title={t("Firmware commit")}
repo={"farmbot-arduino-firmware"} commit={firmware_commit} /> repo={"farmbot-arduino-firmware"} commit={firmware_commit} />
@ -284,6 +296,7 @@ export function FbosDetails(props: FbosDetailsProps) {
{isNumber(memory_usage) && {isNumber(memory_usage) &&
<p><b>{t("Memory usage")}: </b>{memory_usage}MB</p>} <p><b>{t("Memory usage")}: </b>{memory_usage}MB</p>}
{isNumber(disk_usage) && <p><b>{t("Disk usage")}: </b>{disk_usage}%</p>} {isNumber(disk_usage) && <p><b>{t("Disk usage")}: </b>{disk_usage}%</p>}
{isNumber(cpu_usage) && <p><b>{t("CPU usage")}: </b>{cpu_usage}%</p>}
<ChipTemperatureDisplay chip={target} temperature={soc_temp} /> <ChipTemperatureDisplay chip={target} temperature={soc_temp} />
<WiFiStrengthDisplay <WiFiStrengthDisplay
wifiStrength={wifi_level} wifiStrengthPercent={wifi_level_percent} /> wifiStrength={wifi_level} wifiStrengthPercent={wifi_level_percent} />
@ -292,5 +305,9 @@ export function FbosDetails(props: FbosDetailsProps) {
dispatch={props.dispatch} dispatch={props.dispatch}
shouldDisplay={props.shouldDisplay} shouldDisplay={props.shouldDisplay}
sourceFbosConfig={props.sourceFbosConfig} /> sourceFbosConfig={props.sourceFbosConfig} />
{last_ota_checkup && <p><b>{t("Last checked for updates")}: </b>
{reformatDatetime(last_ota_checkup, props.timeSettings)}</p>}
{last_ota && <p><b>{t("Last updated")}: </b>
{reformatDatetime(last_ota, props.timeSettings)}</p>}
</div>; </div>;
} }

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { JobProgress, ConfigurationName } from "farmbot/dist"; import { JobProgress, ConfigurationName } from "farmbot/dist";
import { SemverResult, semverCompare } from "../../../util"; import { SemverResult, semverCompare } from "../../../util";
import { OsUpdateButtonProps } from "./interfaces"; import { OsUpdateButtonProps } from "./interfaces";

View File

@ -4,7 +4,6 @@ import { Collapse, Popover, Position } from "@blueprintjs/core";
import { FactoryResetRow } from "./factory_reset_row"; import { FactoryResetRow } from "./factory_reset_row";
import { PowerAndResetProps } from "./interfaces"; import { PowerAndResetProps } from "./interfaces";
import { ChangeOwnershipForm } from "./change_ownership_form"; import { ChangeOwnershipForm } from "./change_ownership_form";
import { Feature } from "../../interfaces"; import { Feature } from "../../interfaces";
import { FbosButtonRow } from "./fbos_button_row"; import { FbosButtonRow } from "./fbos_button_row";
import { Content } from "../../../constants"; import { Content } from "../../../constants";

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { DangerZoneProps } from "../interfaces"; import { DangerZoneProps } from "../interfaces";
import { Row, Col } from "../../../ui/index"; import { Row, Col } from "../../../ui/index";
import { Header } from "./header"; import { Header } from "./header";

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { McuParams, Encoder, McuParamName } from "farmbot/dist"; import { McuParams, Encoder, McuParamName } from "farmbot/dist";
import { FBSelect, DropDownItem } from "../../../ui/index"; import { FBSelect, DropDownItem } from "../../../ui/index";
import { t } from "../../../i18next_wrapper"; import { t } from "../../../i18next_wrapper";

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { Row, Col } from "../../../ui/index"; import { Row, Col } from "../../../ui/index";
import { t } from "../../../i18next_wrapper"; import { t } from "../../../i18next_wrapper";

View File

@ -17,7 +17,7 @@ export function ZeroButton(props: { axis: Axis; disabled: boolean; }) {
return <button return <button
className="fb-button yellow" className="fb-button yellow"
disabled={disabled} disabled={disabled}
onClick={() => zero(axis)} > onClick={() => zero(axis)}>
{t("zero {{axis}}", { axis })} {t("zero {{axis}}", { axis })}
</button>; </button>;
} }

View File

@ -11,7 +11,7 @@ export function LockableButton({ onClick, disabled, children }: Props) {
return <button return <button
className={"fb-button " + className} className={"fb-button " + className}
disabled={disabled} disabled={disabled}
onClick={() => disabled ? "" : onClick()} > onClick={() => disabled ? "" : onClick()}>
{children} {children}
</button>; </button>;
} }

View File

@ -1,7 +1,6 @@
import * as React from "react"; import * as React from "react";
import { Row, Col } from "../../ui"; import { Row, Col } from "../../ui";
import { ColWidth } from "./farmbot_os_settings"; import { ColWidth } from "./farmbot_os_settings";
import { Collapse } from "@blueprintjs/core"; import { Collapse } from "@blueprintjs/core";
import { Header } from "./hardware_settings/header"; import { Header } from "./hardware_settings/header";
import { ShouldDisplay, Feature } from "../interfaces"; import { ShouldDisplay, Feature } from "../interfaces";

View File

@ -34,7 +34,9 @@ export function botToAPI(stat: string | undefined,
from: "FarmBot", from: "FarmBot",
to: "Web App", to: "Web App",
connectionStatus, connectionStatus,
children: stat ? t("Last message seen ") + `${ago(new Date(stat).getTime())}.` : NOT_SEEN children: stat
? t("Last message seen ") + `${ago(new Date(stat).getTime())}.`
: NOT_SEEN
}; };
} }

View File

@ -134,7 +134,7 @@ export class PinBindingInputGroup
<button <button
className="fb-button green" className="fb-button green"
type="button" type="button"
onClick={this.bindPin} > onClick={this.bindPin}>
{t("BIND")} {t("BIND")}
</button> </button>
</Col> </Col>

View File

@ -83,7 +83,7 @@ export const PinBindings = (props: PinBindingsProps) => {
<Popover <Popover
position={Position.RIGHT_TOP} position={Position.RIGHT_TOP}
interactionKind={PopoverInteractionKind.HOVER} interactionKind={PopoverInteractionKind.HOVER}
popoverClassName={"help"} > popoverClassName={"help"}>
<i className="fa fa-exclamation-triangle" /> <i className="fa fa-exclamation-triangle" />
<div> <div>
{t(ToolTips.PIN_BINDING_WARNING)} {t(ToolTips.PIN_BINDING_WARNING)}

View File

@ -36,7 +36,7 @@ export class DropArea extends React.Component<DropAreaProps, DropAreaState> {
}} }}
onDragOver={this.dragOver} onDragOver={this.dragOver}
onDrop={this.drop} onDrop={this.drop}
style={{ minHeight: "2rem" }} > style={{ minHeight: "2rem" }}>
{this.props.children} {this.props.children}
</div>; </div>;
} }

View File

@ -14,7 +14,7 @@ export const NULL_DRAGGER_ID = 0xCAFEF00D;
* Example usage: * Example usage:
* *
* <button draggable={true} * <button draggable={true}
* onDragStart={stepDragEventHandler(dispatch, step, "optnl-stuff")} > * onDragStart={stepDragEventHandler(dispatch, step, "optnl-stuff")}>
* Drag this! * Drag this!
* </button> * </button>
* */ * */
@ -36,7 +36,7 @@ export function StepDragger({ dispatch,
onDragStart={stepDragEventHandler(dispatch, onDragStart={stepDragEventHandler(dispatch,
step, step,
intent, intent,
draggerId)} > draggerId)}>
{children} {children}
</div>; </div>;
} }

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { Widget, WidgetHeader, WidgetBody } from "../ui"; import { Widget, WidgetHeader, WidgetBody } from "../ui";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";

View File

@ -1,36 +1,24 @@
jest.mock("../point_groups/group_detail", () => ({
jest.mock("../../api/crud", () => { fetchGroupFromUrl: jest.fn(() => mockGroup)
return { overwrite: jest.fn() }; }));
});
jest.mock("../point_groups/group_detail", () => {
return {
fetchGroupFromUrl: jest.fn(() => mockGroup)
};
});
jest.mock("../../api/crud", () => ({ jest.mock("../../api/crud", () => ({
overwrite: jest.fn(), overwrite: jest.fn(),
edit: jest.fn() edit: jest.fn(),
})); }));
let mockMode = "none"; let mockMode = "none";
jest.mock("../map/util", () => ({ getMode: jest.fn(() => mockMode) }));
jest.mock("../map/util", () => { import {
return { fakePlant, fakePointGroup
getMode: jest.fn(() => mockMode) } from "../../__test_support__/fake_state/resources";
};
});
import { fakePlant, fakePointGroup } from "../../__test_support__/fake_state/resources";
import { fakeState } from "../../__test_support__/fake_state"; import { fakeState } from "../../__test_support__/fake_state";
import { GetState } from "../../redux/interfaces"; import { GetState } from "../../redux/interfaces";
import { clickMapPlant, selectPlant, toggleHoveredPlant } from "../actions";
import { import {
clickMapPlant, buildResourceIndex
selectPlant, } from "../../__test_support__/resource_index_builder";
toggleHoveredPlant
} from "../actions";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
import { overwrite } from "../../api/crud"; import { overwrite } from "../../api/crud";
const mockGroup = fakePointGroup(); const mockGroup = fakePointGroup();

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
let mockPath = "/app/designer/plants"; let mockPath = "/app/designer/plants";
jest.mock("../../history", () => ({ jest.mock("../../history", () => ({
history: { getCurrentLocation: jest.fn(() => ({ pathname: mockPath })) }, history: { getCurrentLocation: jest.fn(() => ({ pathname: mockPath })) },
@ -11,8 +9,10 @@ jest.mock("../../api/crud", () => ({
save: jest.fn(), save: jest.fn(),
})); }));
jest.mock("../plants/plant_inventory", () => ({ Plants: () => <div /> }));
import * as React from "react"; import * as React from "react";
import { RawFarmDesigner } from "../index"; import { RawFarmDesigner as FarmDesigner } from "../index";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { Props } from "../interfaces"; import { Props } from "../interfaces";
import { GardenMapLegendProps } from "../map/interfaces"; import { GardenMapLegendProps } from "../map/interfaces";
@ -22,48 +22,47 @@ import {
} from "../../__test_support__/fake_state/resources"; } from "../../__test_support__/fake_state/resources";
import { fakeDesignerState } from "../../__test_support__/fake_designer_state"; import { fakeDesignerState } from "../../__test_support__/fake_designer_state";
import { fakeTimeSettings } from "../../__test_support__/fake_time_settings"; import { fakeTimeSettings } from "../../__test_support__/fake_time_settings";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder"; import {
buildResourceIndex
} from "../../__test_support__/resource_index_builder";
import { fakeState } from "../../__test_support__/fake_state"; import { fakeState } from "../../__test_support__/fake_state";
import { edit } from "../../api/crud"; import { edit } from "../../api/crud";
import { BooleanSetting } from "../../session_keys"; import { BooleanSetting } from "../../session_keys";
describe("<RawFarmDesigner/>", () => { describe("<FarmDesigner/>", () => {
function fakeProps(): Props { const fakeProps = (): Props => ({
dispatch: jest.fn(),
return { selectedPlant: undefined,
dispatch: jest.fn(), designer: fakeDesignerState(),
selectedPlant: undefined, hoveredPlant: undefined,
designer: fakeDesignerState(), points: [],
hoveredPlant: undefined, plants: [],
points: [], toolSlots: [],
plants: [], crops: [],
toolSlots: [], botLocationData: {
crops: [], position: { x: undefined, y: undefined, z: undefined },
botLocationData: { scaled_encoders: { x: undefined, y: undefined, z: undefined },
position: { x: undefined, y: undefined, z: undefined }, raw_encoders: { x: undefined, y: undefined, z: undefined },
scaled_encoders: { x: undefined, y: undefined, z: undefined }, },
raw_encoders: { x: undefined, y: undefined, z: undefined }, botMcuParams: bot.hardware.mcu_params,
}, stepsPerMmXY: { x: undefined, y: undefined },
botMcuParams: bot.hardware.mcu_params, peripherals: [],
stepsPerMmXY: { x: undefined, y: undefined }, eStopStatus: false,
peripherals: [], latestImages: [],
eStopStatus: false, cameraCalibrationData: {
latestImages: [], scale: undefined, rotation: undefined,
cameraCalibrationData: { offset: { x: undefined, y: undefined },
scale: undefined, rotation: undefined, origin: undefined,
offset: { x: undefined, y: undefined }, calibrationZ: undefined
origin: undefined, },
calibrationZ: undefined timeSettings: fakeTimeSettings(),
}, getConfigValue: jest.fn(),
timeSettings: fakeTimeSettings(), sensorReadings: [],
getConfigValue: jest.fn(), sensors: [],
sensorReadings: [], });
sensors: [],
};
}
it("loads default map settings", () => { it("loads default map settings", () => {
const wrapper = mount(<RawFarmDesigner {...fakeProps()} />); const wrapper = mount(<FarmDesigner {...fakeProps()} />);
const legendProps = const legendProps =
wrapper.find("GardenMapLegend").props() as GardenMapLegendProps; wrapper.find("GardenMapLegend").props() as GardenMapLegendProps;
expect(legendProps.legendMenuOpen).toBeFalsy(); expect(legendProps.legendMenuOpen).toBeFalsy();
@ -86,7 +85,7 @@ describe("<RawFarmDesigner/>", () => {
image1.body.created_at = "2001-01-03T00:00:00.000Z"; image1.body.created_at = "2001-01-03T00:00:00.000Z";
image2.body.created_at = "2001-01-01T00:00:00.000Z"; image2.body.created_at = "2001-01-01T00:00:00.000Z";
p.latestImages = [image1, image2]; p.latestImages = [image1, image2];
const wrapper = mount(<RawFarmDesigner {...p} />); const wrapper = mount(<FarmDesigner {...p} />);
const legendProps = const legendProps =
wrapper.find("GardenMapLegend").props() as GardenMapLegendProps; wrapper.find("GardenMapLegend").props() as GardenMapLegendProps;
expect(legendProps.imageAgeInfo) expect(legendProps.imageAgeInfo)
@ -95,28 +94,34 @@ describe("<RawFarmDesigner/>", () => {
it("renders nav titles", () => { it("renders nav titles", () => {
mockPath = "/app/designer/plants"; mockPath = "/app/designer/plants";
const wrapper = mount(<RawFarmDesigner {...fakeProps()} />); const wrapper = mount(<FarmDesigner {...fakeProps()} />);
["Map", "Plants", "Events"].map(string => ["Map", "Plants", "Events"].map(string =>
expect(wrapper.text()).toContain(string)); expect(wrapper.text()).toContain(string));
expect(wrapper.find(".panel-nav").first().hasClass("hidden")).toBeTruthy(); expect(wrapper.find(".panel-nav").first().hasClass("hidden"))
expect(wrapper.find(".farm-designer-panels").hasClass("panel-open")).toBeTruthy(); .toBeTruthy();
expect(wrapper.find(".farm-designer-map").hasClass("panel-open")).toBeTruthy(); expect(wrapper.find(".farm-designer-panels").hasClass("panel-open"))
.toBeTruthy();
expect(wrapper.find(".farm-designer-map").hasClass("panel-open"))
.toBeTruthy();
}); });
it("hides panel", () => { it("hides panel", () => {
mockPath = "/app/designer"; mockPath = "/app/designer";
const wrapper = mount(<RawFarmDesigner {...fakeProps()} />); const wrapper = mount(<FarmDesigner {...fakeProps()} />);
["Map", "Plants", "Events"].map(string => ["Map", "Plants", "Events"].map(string =>
expect(wrapper.text()).toContain(string)); expect(wrapper.text()).toContain(string));
expect(wrapper.find(".panel-nav").first().hasClass("hidden")).toBeFalsy(); expect(wrapper.find(".panel-nav").first().hasClass("hidden"))
expect(wrapper.find(".farm-designer-panels").hasClass("panel-open")).toBeFalsy(); .toBeFalsy();
expect(wrapper.find(".farm-designer-map").hasClass("panel-open")).toBeFalsy(); expect(wrapper.find(".farm-designer-panels").hasClass("panel-open"))
.toBeFalsy();
expect(wrapper.find(".farm-designer-map").hasClass("panel-open"))
.toBeFalsy();
}); });
it("renders saved garden indicator", () => { it("renders saved garden indicator", () => {
const p = fakeProps(); const p = fakeProps();
p.designer.openedSavedGarden = "SavedGardenUuid"; p.designer.openedSavedGarden = "SavedGardenUuid";
const wrapper = mount(<RawFarmDesigner {...p} />); const wrapper = mount(<FarmDesigner {...p} />);
expect(wrapper.text().toLowerCase()).toContain("viewing saved garden"); expect(wrapper.text().toLowerCase()).toContain("viewing saved garden");
}); });
@ -126,8 +131,10 @@ describe("<RawFarmDesigner/>", () => {
const dispatch = jest.fn(); const dispatch = jest.fn();
state.resources = buildResourceIndex([fakeWebAppConfig()]); state.resources = buildResourceIndex([fakeWebAppConfig()]);
p.dispatch = jest.fn(x => x(dispatch, () => state)); p.dispatch = jest.fn(x => x(dispatch, () => state));
const wrapper = mount<RawFarmDesigner>(<RawFarmDesigner {...p} />); const wrapper = mount<FarmDesigner>(<FarmDesigner {...p} />);
wrapper.instance().toggle(BooleanSetting.show_plants)(); wrapper.instance().toggle(BooleanSetting.show_plants)();
expect(edit).toHaveBeenCalledWith(expect.any(Object), { bot_origin_quadrant: 2 }); expect(edit).toHaveBeenCalledWith(expect.any(Object), {
bot_origin_quadrant: 2
});
}); });
}); });

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
const mockDevice = { moveAbsolute: jest.fn(() => Promise.resolve()) }; const mockDevice = { moveAbsolute: jest.fn(() => Promise.resolve()) };
jest.mock("../../device", () => ({ getDevice: () => mockDevice })); jest.mock("../../device", () => ({ getDevice: () => mockDevice }));
@ -12,8 +10,8 @@ jest.mock("../../history", () => ({
import * as React from "react"; import * as React from "react";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { import {
MoveTo, MoveToProps, MoveToForm, MoveToFormProps, MoveModeLink, chooseLocation, RawMoveTo as MoveTo, MoveToProps, MoveToForm, MoveToFormProps,
mapStateToProps MoveModeLink, chooseLocation, mapStateToProps,
} from "../move_to"; } from "../move_to";
import { history } from "../../history"; import { history } from "../../history";
import { Actions } from "../../constants"; import { Actions } from "../../constants";

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../../config_storage/actions", () => ({ jest.mock("../../config_storage/actions", () => ({
getWebAppConfigValue: jest.fn(x => { x(); return jest.fn(() => true); }), getWebAppConfigValue: jest.fn(x => { x(); return jest.fn(() => true); }),
setWebAppConfigValue: jest.fn(), setWebAppConfigValue: jest.fn(),
@ -8,7 +6,8 @@ jest.mock("../../config_storage/actions", () => ({
import * as React from "react"; import * as React from "react";
import { mount, ReactWrapper } from "enzyme"; import { mount, ReactWrapper } from "enzyme";
import { import {
RawDesignerSettings, DesignerSettingsProps, mapStateToProps RawDesignerSettings as DesignerSettings, DesignerSettingsProps,
mapStateToProps,
} from "../settings"; } from "../settings";
import { fakeState } from "../../__test_support__/fake_state"; import { fakeState } from "../../__test_support__/fake_state";
import { BooleanSetting, NumericSetting } from "../../session_keys"; import { BooleanSetting, NumericSetting } from "../../session_keys";
@ -22,14 +21,14 @@ const getSetting =
return setting; return setting;
}; };
describe("<RawDesignerSettings />", () => { describe("<DesignerSettings />", () => {
const fakeProps = (): DesignerSettingsProps => ({ const fakeProps = (): DesignerSettingsProps => ({
dispatch: jest.fn(), dispatch: jest.fn(),
getConfigValue: jest.fn(), getConfigValue: jest.fn(),
}); });
it("renders settings", () => { it("renders settings", () => {
const wrapper = mount(<RawDesignerSettings {...fakeProps()} />); const wrapper = mount(<DesignerSettings {...fakeProps()} />);
expect(wrapper.text()).toContain("size"); expect(wrapper.text()).toContain("size");
const settings = wrapper.find(".designer-setting"); const settings = wrapper.find(".designer-setting");
expect(settings.length).toEqual(7); expect(settings.length).toEqual(7);
@ -38,13 +37,13 @@ describe("<RawDesignerSettings />", () => {
it("renders defaultOn setting", () => { it("renders defaultOn setting", () => {
const p = fakeProps(); const p = fakeProps();
p.getConfigValue = () => undefined; p.getConfigValue = () => undefined;
const wrapper = mount(<RawDesignerSettings {...p} />); const wrapper = mount(<DesignerSettings {...p} />);
const confirmDeletion = getSetting(wrapper, 6, "confirm plant"); const confirmDeletion = getSetting(wrapper, 6, "confirm plant");
expect(confirmDeletion.find("button").text()).toEqual("on"); expect(confirmDeletion.find("button").text()).toEqual("on");
}); });
it("toggles setting", () => { it("toggles setting", () => {
const wrapper = mount(<RawDesignerSettings {...fakeProps()} />); const wrapper = mount(<DesignerSettings {...fakeProps()} />);
const trailSetting = getSetting(wrapper, 1, "trail"); const trailSetting = getSetting(wrapper, 1, "trail");
trailSetting.find("button").simulate("click"); trailSetting.find("button").simulate("click");
expect(setWebAppConfigValue) expect(setWebAppConfigValue)
@ -54,7 +53,7 @@ describe("<RawDesignerSettings />", () => {
it("changes origin", () => { it("changes origin", () => {
const p = fakeProps(); const p = fakeProps();
p.getConfigValue = () => 2; p.getConfigValue = () => 2;
const wrapper = mount(<RawDesignerSettings {...p} />); const wrapper = mount(<DesignerSettings {...p} />);
const originSetting = getSetting(wrapper, 5, "origin"); const originSetting = getSetting(wrapper, 5, "origin");
originSetting.find("div").last().simulate("click"); originSetting.find("div").last().simulate("click");
expect(setWebAppConfigValue).toHaveBeenCalledWith( expect(setWebAppConfigValue).toHaveBeenCalledWith(

View File

@ -1,10 +1,8 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../../../history", () => ({ history: { push: jest.fn() } })); jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
import * as React from "react"; import * as React from "react";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { AddFarmEvent } from "../add_farm_event"; import { RawAddFarmEvent as AddFarmEvent } from "../add_farm_event";
import { AddEditFarmEventProps } from "../../interfaces"; import { AddEditFarmEventProps } from "../../interfaces";
import { import {
fakeFarmEvent, fakeSequence, fakeRegimen fakeFarmEvent, fakeSequence, fakeRegimen

View File

@ -1,16 +1,10 @@
jest.mock("react-redux", () => ({
connect: jest.fn(() => (x: {}) => x)
}));
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
history: { history: { push: jest.fn() }
push: jest.fn()
}
})); }));
import * as React from "react"; import * as React from "react";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { EditFarmEvent } from "../edit_farm_event"; import { RawEditFarmEvent as EditFarmEvent } from "../edit_farm_event";
import { AddEditFarmEventProps } from "../../interfaces"; import { AddEditFarmEventProps } from "../../interfaces";
import { import {
fakeFarmEvent, fakeSequence fakeFarmEvent, fakeSequence

View File

@ -2,12 +2,13 @@ import { Regimen } from "../../../regimens/interfaces";
import { Sequence } from "../../../sequences/interfaces"; import { Sequence } from "../../../sequences/interfaces";
import { FarmEvent } from "farmbot/dist/resources/api_resources"; import { FarmEvent } from "farmbot/dist/resources/api_resources";
/** Would it be better to make a fully formed farm event? Join regimen, sequence, etc. */ /** Make a fully formed farm event: join regimen, sequence, etc. */
/** STEP 1: Extract querying of data and formatting of data into two /** STEP 1: Extract querying of data and formatting of data into two
* sep. function. This function will join `executable` on `farm_event`. */ * sep. function. This function will join `executable` on `farm_event`. */
export type FarmEventWithExecutable = FarmEventWithRegimen | FarmEventWithSequence; export type FarmEventWithExecutable =
FarmEventWithRegimen | FarmEventWithSequence;
/** Takes a farm event and merges it with its sequence object. */ /** Takes a farm event and merges it with its sequence object. */
export interface FarmEventWithSequence extends FarmEvent { export interface FarmEventWithSequence extends FarmEvent {

View File

@ -27,9 +27,8 @@ export function joinFarmEventsToExecutable(
executable_type: body.executable_type, executable_type: body.executable_type,
executable: executable1.body executable: executable1.body
}; };
} else {
throw new Error("Bad executable ID (sequence): " + id);
} }
break;
case "Regimen": case "Regimen":
const executable2 = regimenById[id]; const executable2 = regimenById[id];
if (executable2) { if (executable2) {
@ -38,12 +37,9 @@ export function joinFarmEventsToExecutable(
executable_type: body.executable_type, executable_type: body.executable_type,
executable: executable2.body executable: executable2.body
}; };
} else {
throw new Error("Bad executable ID (regimen): " + id);
} }
} }
} else {
throw new Error("Farmevent had no ID");
} }
throw new Error("Impossible?");
})); }));
} }

View File

@ -10,7 +10,9 @@ import {
maybeGetTimeSettings, maybeGetTimeSettings,
} from "../../resources/selectors"; } from "../../resources/selectors";
import { ResourceIndex } from "../../resources/interfaces"; import { ResourceIndex } from "../../resources/interfaces";
import { FarmEventWithRegimen, FarmEventWithSequence } from "./calendar/interfaces"; import {
FarmEventWithRegimen, FarmEventWithSequence
} from "./calendar/interfaces";
import { scheduleForFarmEvent } from "./calendar/scheduler"; import { scheduleForFarmEvent } from "./calendar/scheduler";
import { last } from "lodash"; import { last } from "lodash";
import { RegimenItem } from "../../regimens/interfaces"; import { RegimenItem } from "../../regimens/interfaces";
@ -63,7 +65,8 @@ export const nextRegItemTimes =
&& time.isSameOrAfter(moment(startTime))); && time.isSameOrAfter(moment(startTime)));
}; };
export let regimenCalendarAdder = (index: ResourceIndex, timeSettings: TimeSettings) => export let regimenCalendarAdder = (
index: ResourceIndex, timeSettings: TimeSettings) =>
(f: FarmEventWithRegimen, c: Calendar, now = moment()) => { (f: FarmEventWithRegimen, c: Calendar, now = moment()) => {
const { regimen_items } = f.executable; const { regimen_items } = f.executable;
const gracePeriod = itemGracePeriod(now); const gracePeriod = itemGracePeriod(now);

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { timezoneMismatch } from "../../devices/timezones/guess_timezone"; import { timezoneMismatch } from "../../devices/timezones/guess_timezone";
import { Content } from "../../constants"; import { Content } from "../../constants";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { transformXY } from "../util"; import { transformXY } from "../util";
import { MapTransformProps, BotSize } from "../interfaces"; import { MapTransformProps, BotSize } from "../interfaces";
import { random, range, some, clamp, sample } from "lodash"; import { random, range, some, clamp, sample } from "lodash";

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
let mockPath = ""; let mockPath = "";
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
history: { push: jest.fn() }, history: { push: jest.fn() },
@ -8,7 +6,7 @@ jest.mock("../../../history", () => ({
import * as React from "react"; import * as React from "react";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { AddPlant, AddPlantProps } from "../add_plant"; import { RawAddPlant as AddPlant, AddPlantProps } from "../add_plant";
import { history } from "../../../history"; import { history } from "../../../history";
import { import {
fakeCropLiveSearchResult fakeCropLiveSearchResult

View File

@ -1,10 +1,4 @@
jest.mock("react-redux", () => ({ jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));
connect: jest.fn(() => (x: {}) => x)
}));
jest.mock("../../../api/crud", () => ({
initSave: jest.fn()
}));
jest.mock("../../../farmware/weed_detector/actions", () => ({ jest.mock("../../../farmware/weed_detector/actions", () => ({
deletePoints: jest.fn() deletePoints: jest.fn()
@ -49,14 +43,12 @@ describe("mapStateToProps", () => {
}); });
describe("<CreatePoints />", () => { describe("<CreatePoints />", () => {
const fakeProps = (): CreatePointsProps => { const fakeProps = (): CreatePointsProps => ({
return { dispatch: jest.fn(),
dispatch: jest.fn(), currentPoint: undefined,
currentPoint: undefined, deviceY: 1.23,
deviceY: 1.23, deviceX: 3.21
deviceX: 3.21 });
};
};
const fakeInstance = () => { const fakeInstance = () => {
const props = fakeProps(); const props = fakeProps();

View File

@ -1,7 +1,3 @@
jest.mock("react-redux", () => ({
connect: jest.fn(() => (x: {}) => x)
}));
jest.mock("lodash", () => ({ jest.mock("lodash", () => ({
debounce: jest.fn(x => x), debounce: jest.fn(x => x),
trim: jest.fn(x => x), trim: jest.fn(x => x),
@ -14,7 +10,7 @@ jest.mock("lodash", () => ({
jest.mock("../../../history", () => ({ history: { push: jest.fn() } })); jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
import * as React from "react"; import * as React from "react";
import { CropCatalog } from "../crop_catalog"; import { RawCropCatalog as CropCatalog } from "../crop_catalog";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { CropCatalogProps } from "../../interfaces"; import { CropCatalogProps } from "../../interfaces";
import { Actions } from "../../../constants"; import { Actions } from "../../../constants";

View File

@ -1,14 +1,10 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
let mockPath = ""; let mockPath = "";
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
getPathArray: jest.fn(() => { return mockPath.split("/"); }), getPathArray: jest.fn(() => { return mockPath.split("/"); }),
history: { push: jest.fn() } history: { push: jest.fn() }
})); }));
jest.mock("../../../api/crud", () => ({ jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));
initSave: jest.fn()
}));
jest.mock("../../actions", () => ({ jest.mock("../../actions", () => ({
unselectPlant: jest.fn(() => jest.fn()), unselectPlant: jest.fn(() => jest.fn()),
@ -16,7 +12,7 @@ jest.mock("../../actions", () => ({
})); }));
import * as React from "react"; import * as React from "react";
import { CropInfo, searchForCurrentCrop } from "../crop_info"; import { RawCropInfo as CropInfo, searchForCurrentCrop } from "../crop_info";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { CropInfoProps } from "../../interfaces"; import { CropInfoProps } from "../../interfaces";
import { initSave } from "../../../api/crud"; import { initSave } from "../../../api/crud";

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
let mockPath = "/app/designer/plants/1"; let mockPath = "/app/designer/plants/1";
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
getPathArray: jest.fn(() => mockPath.split("/")), getPathArray: jest.fn(() => mockPath.split("/")),

View File

@ -1,7 +1,5 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
import * as React from "react"; import * as React from "react";
import { RawPlants, PlantInventoryProps } from "../plant_inventory"; import { RawPlants as Plants, PlantInventoryProps } from "../plant_inventory";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { fakePlant } from "../../../__test_support__/fake_state/resources"; import { fakePlant } from "../../../__test_support__/fake_state/resources";
@ -13,7 +11,7 @@ describe("<PlantInventory />", () => {
}); });
it("renders", () => { it("renders", () => {
const wrapper = mount(<RawPlants {...fakeProps()} />); const wrapper = mount(<Plants {...fakeProps()} />);
["Map", ["Map",
"Plants", "Plants",
"Events", "Events",
@ -25,13 +23,13 @@ describe("<PlantInventory />", () => {
}); });
it("has link to crops", () => { it("has link to crops", () => {
const wrapper = mount(<RawPlants {...fakeProps()} />); const wrapper = mount(<Plants {...fakeProps()} />);
expect(wrapper.html()).toContain("fa-plus"); expect(wrapper.html()).toContain("fa-plus");
expect(wrapper.html()).toContain("/app/designer/plants/crop_search"); expect(wrapper.html()).toContain("/app/designer/plants/crop_search");
}); });
it("updates search term", () => { it("updates search term", () => {
const wrapper = shallow<RawPlants>(<RawPlants {...fakeProps()} />); const wrapper = shallow<Plants>(<Plants {...fakeProps()} />);
expect(wrapper.state().searchTerm).toEqual(""); expect(wrapper.state().searchTerm).toEqual("");
wrapper.find("input").first().simulate("change", wrapper.find("input").first().simulate("change",
{ currentTarget: { value: "mint" } }); { currentTarget: { value: "mint" } });

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
let mockPath = "/app/designer/points/1"; let mockPath = "/app/designer/points/1";
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
getPathArray: jest.fn(() => mockPath.split("/")), getPathArray: jest.fn(() => mockPath.split("/")),
@ -7,17 +5,20 @@ jest.mock("../../../history", () => ({
})); }));
const mockMoveAbs = jest.fn(); const mockMoveAbs = jest.fn();
jest.mock("../../../device", () => ({
jest.mock("../../../device", () => { getDevice: () => ({ moveAbsolute: mockMoveAbs })
return { getDevice: () => ({ moveAbsolute: mockMoveAbs }) }; }));
});
import * as React from "react"; import * as React from "react";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { EditPoint, EditPointProps, mapStateToProps, moveToPoint } from "../point_info"; import {
RawEditPoint as EditPoint, EditPointProps, mapStateToProps, moveToPoint
} from "../point_info";
import { fakePoint } from "../../../__test_support__/fake_state/resources"; import { fakePoint } from "../../../__test_support__/fake_state/resources";
import { fakeState } from "../../../__test_support__/fake_state"; import { fakeState } from "../../../__test_support__/fake_state";
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder"; import {
buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
import { getDevice } from "../../../device"; import { getDevice } from "../../../device";
describe("<EditPoint />", () => { describe("<EditPoint />", () => {

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
push: jest.fn(), push: jest.fn(),
getPathArray: () => [], getPathArray: () => [],
@ -7,7 +5,7 @@ jest.mock("../../../history", () => ({
import * as React from "react"; import * as React from "react";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { RawPoints, PointsProps } from "../point_inventory"; import { RawPoints as Points, PointsProps } from "../point_inventory";
import { fakePoint } from "../../../__test_support__/fake_state/resources"; import { fakePoint } from "../../../__test_support__/fake_state/resources";
import { push } from "../../../history"; import { push } from "../../../history";
import { fakeState } from "../../../__test_support__/fake_state"; import { fakeState } from "../../../__test_support__/fake_state";
@ -16,21 +14,21 @@ import {
} from "../../../__test_support__/resource_index_builder"; } from "../../../__test_support__/resource_index_builder";
import { mapStateToProps } from "../point_inventory"; import { mapStateToProps } from "../point_inventory";
describe("<RawPoints> />", () => { describe("<Points> />", () => {
const fakeProps = (): PointsProps => ({ const fakeProps = (): PointsProps => ({
points: [], points: [],
dispatch: jest.fn(), dispatch: jest.fn(),
}); });
it("renders no points", () => { it("renders no points", () => {
const wrapper = mount(<RawPoints {...fakeProps()} />); const wrapper = mount(<Points {...fakeProps()} />);
expect(wrapper.text()).toContain("No points yet."); expect(wrapper.text()).toContain("No points yet.");
}); });
it("renders points", () => { it("renders points", () => {
const p = fakeProps(); const p = fakeProps();
p.points = [fakePoint()]; p.points = [fakePoint()];
const wrapper = mount(<RawPoints {...p} />); const wrapper = mount(<Points {...p} />);
expect(wrapper.text()).toContain("Point 1"); expect(wrapper.text()).toContain("Point 1");
}); });
@ -38,7 +36,7 @@ describe("<RawPoints> />", () => {
const p = fakeProps(); const p = fakeProps();
p.points = [fakePoint()]; p.points = [fakePoint()];
p.points[0].body.id = 1; p.points[0].body.id = 1;
const wrapper = mount(<RawPoints {...p} />); const wrapper = mount(<Points {...p} />);
wrapper.find(".point-search-item").first().simulate("click"); wrapper.find(".point-search-item").first().simulate("click");
expect(push).toHaveBeenCalledWith("/app/designer/points/1"); expect(push).toHaveBeenCalledWith("/app/designer/points/1");
}); });
@ -48,7 +46,7 @@ describe("<RawPoints> />", () => {
p.points = [fakePoint(), fakePoint()]; p.points = [fakePoint(), fakePoint()];
p.points[0].body.name = "point 0"; p.points[0].body.name = "point 0";
p.points[1].body.name = "point 1"; p.points[1].body.name = "point 1";
const wrapper = shallow<RawPoints>(<RawPoints {...p} />); const wrapper = shallow<Points>(<Points {...p} />);
wrapper.find("input").first().simulate("change", wrapper.find("input").first().simulate("change",
{ currentTarget: { value: "0" } }); { currentTarget: { value: "0" } });
expect(wrapper.state().searchTerm).toEqual("0"); expect(wrapper.state().searchTerm).toEqual("0");
@ -59,7 +57,7 @@ describe("<RawPoints> />", () => {
p.points = [fakePoint(), fakePoint()]; p.points = [fakePoint(), fakePoint()];
p.points[0].body.name = "point 0"; p.points[0].body.name = "point 0";
p.points[1].body.name = "point 1"; p.points[1].body.name = "point 1";
const wrapper = mount(<RawPoints {...p} />); const wrapper = mount(<Points {...p} />);
wrapper.setState({ searchTerm: "0" }); wrapper.setState({ searchTerm: "0" });
expect(wrapper.text()).not.toContain("point 1"); expect(wrapper.text()).not.toContain("point 1");
}); });

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
let mockPath = ""; let mockPath = "";
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
history: { push: jest.fn() }, history: { push: jest.fn() },
@ -17,7 +15,9 @@ jest.mock("../../point_groups/actions", () => ({ createGroup: jest.fn() }));
import * as React from "react"; import * as React from "react";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { SelectPlants, SelectPlantsProps } from "../select_plants"; import {
RawSelectPlants as SelectPlants, SelectPlantsProps
} from "../select_plants";
import { fakePlant } from "../../../__test_support__/fake_state/resources"; import { fakePlant } from "../../../__test_support__/fake_state/resources";
import { Actions } from "../../../constants"; import { Actions } from "../../../constants";
import { clickButton } from "../../../__test_support__/helpers"; import { clickButton } from "../../../__test_support__/helpers";

View File

@ -1,7 +1,6 @@
import * as React from "react"; import * as React from "react";
import { Everything } from "../../interfaces"; import { Everything } from "../../interfaces";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { history } from "../../history"; import { history } from "../../history";
import { svgToUrl } from "../../open_farm/icons"; import { svgToUrl } from "../../open_farm/icons";
import { CropLiveSearchResult, OpenfarmSearch } from "../interfaces"; import { CropLiveSearchResult, OpenfarmSearch } from "../interfaces";

View File

@ -1,7 +1,6 @@
import * as React from "react"; import * as React from "react";
import { Everything } from "../../interfaces"; import { Everything } from "../../interfaces";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { OpenFarmResults } from "./openfarm_search_results"; import { OpenFarmResults } from "./openfarm_search_results";
import { CropCatalogProps } from "../interfaces"; import { CropCatalogProps } from "../interfaces";
import { OFSearch } from "../util"; import { OFSearch } from "../util";

View File

@ -170,7 +170,7 @@ const EditOnOpenFarm = ({ slug }: { slug: string }) =>
<div className="edit-on-openfarm"> <div className="edit-on-openfarm">
<span>{t("Edit on")}&nbsp;</span> <span>{t("Edit on")}&nbsp;</span>
<a href={OpenFarm.browsingCropUrl + slug} target="_blank" <a href={OpenFarm.browsingCropUrl + slug} target="_blank"
title={t("Open OpenFarm.cc in a new tab")} > title={t("Open OpenFarm.cc in a new tab")}>
{"OpenFarm"} {"OpenFarm"}
</a> </a>
</div>; </div>;

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { PlantInventoryItem } from "./plant_inventory_item"; import { PlantInventoryItem } from "./plant_inventory_item";
import { Everything } from "../../interfaces"; import { Everything } from "../../interfaces";
import { Panel, DesignerNavTabs } from "../panel_header"; import { Panel, DesignerNavTabs } from "../panel_header";

View File

@ -157,7 +157,7 @@ const DeleteButtons = (props: DeleteButtonsProps) =>
<button <button
className="fb-button gray no-float" className="fb-button gray no-float"
style={{ marginRight: "10px" }} style={{ marginRight: "10px" }}
onClick={() => history.push("/app/designer/plants/select")} > onClick={() => history.push("/app/designer/plants/select")}>
{t("Delete multiple")} {t("Delete multiple")}
</button> </button>
</div>; </div>;

View File

@ -1,14 +1,13 @@
const mockId = 123; const mockId = 123;
jest.mock("../../../history", () => ({
jest.mock("../../../history", () => { getPathArray: jest.fn(() => [mockId])
return { }));
getPathArray: jest.fn(() => [mockId])
};
});
import { fetchGroupFromUrl } from "../group_detail"; import { fetchGroupFromUrl } from "../group_detail";
import { fakePointGroup } from "../../../__test_support__/fake_state/resources"; import { fakePointGroup } from "../../../__test_support__/fake_state/resources";
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder"; import {
buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
describe("fetchGroupFromUrl", () => { describe("fetchGroupFromUrl", () => {
it("fetches a group from URL", () => { it("fetches a group from URL", () => {

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
getPathArray: jest.fn(() => ["L", "O", "L"]), getPathArray: jest.fn(() => ["L", "O", "L"]),
history: { push: jest.fn() } history: { push: jest.fn() }
@ -7,11 +5,15 @@ jest.mock("../../../history", () => ({
import React from "react"; import React from "react";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { RawGroupListPanel as GroupListPanel, GroupListPanelProps, mapStateToProps } from "../group_list_panel"; import {
RawGroupListPanel as GroupListPanel, GroupListPanelProps, mapStateToProps
} from "../group_list_panel";
import { fakePointGroup } from "../../../__test_support__/fake_state/resources"; import { fakePointGroup } from "../../../__test_support__/fake_state/resources";
import { history } from "../../../history"; import { history } from "../../../history";
import { fakeState } from "../../../__test_support__/fake_state"; import { fakeState } from "../../../__test_support__/fake_state";
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder"; import {
buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
describe("<GroupListPanel />", () => { describe("<GroupListPanel />", () => {
const fakeProps = (): GroupListPanelProps => { const fakeProps = (): GroupListPanelProps => {

View File

@ -53,8 +53,8 @@ export const LittleIcon =
</span>; </span>;
}; };
export class GroupDetailActive extends React.Component<GroupDetailActiveProps, State> { export class GroupDetailActive
extends React.Component<GroupDetailActiveProps, State> {
state: State = { icons: {} }; state: State = { icons: {} };
update = ({ currentTarget }: React.SyntheticEvent<HTMLInputElement>) => { update = ({ currentTarget }: React.SyntheticEvent<HTMLInputElement>) => {
@ -126,7 +126,9 @@ export class GroupDetailActive extends React.Component<GroupDetailActiveProps, S
<input <input
defaultValue={this.name} defaultValue={this.name}
onChange={this.update} /> onChange={this.update} />
<label>{t("GROUP MEMBERS ({{count}})", { count: this.icons.length })}</label> <label>
{t("GROUP MEMBERS ({{count}})", { count: this.icons.length })}
</label>
<p> <p>
{t("Click plants in map to add or remove.")} {t("Click plants in map to add or remove.")}
</p> </p>

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../actions", () => ({ jest.mock("../actions", () => ({
snapshotGarden: jest.fn(), snapshotGarden: jest.fn(),
applyGarden: jest.fn(), applyGarden: jest.fn(),
@ -25,7 +23,8 @@ jest.mock("../../../account/dev/dev_support", () => ({
import * as React from "react"; import * as React from "react";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { import {
SavedGardens, mapStateToProps, SavedGardensLink, SavedGardenHUD, savedGardenOpen RawSavedGardens as SavedGardens, mapStateToProps, SavedGardensLink,
SavedGardenHUD, savedGardenOpen,
} from "../saved_gardens"; } from "../saved_gardens";
import { clickButton } from "../../../__test_support__/helpers"; import { clickButton } from "../../../__test_support__/helpers";
import { import {

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { snapshotGarden, newSavedGarden, copySavedGarden } from "./actions"; import { snapshotGarden, newSavedGarden, copySavedGarden } from "./actions";
import { TaggedPlantTemplate, TaggedSavedGarden } from "farmbot"; import { TaggedPlantTemplate, TaggedSavedGarden } from "farmbot";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";

View File

@ -1,5 +1,4 @@
import { CropLiveSearchResult } from "./interfaces"; import { CropLiveSearchResult } from "./interfaces";
import { DEFAULT_ICON } from "../open_farm/icons"; import { DEFAULT_ICON } from "../open_farm/icons";
import { startCase, find } from "lodash"; import { startCase, find } from "lodash";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";

View File

@ -1,10 +1,10 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../../../api/crud", () => ({ initSave: jest.fn() })); jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));
import * as React from "react"; import * as React from "react";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { RawAddTool as AddTool, AddToolProps, mapStateToProps } from "../add_tool"; import {
RawAddTool as AddTool, AddToolProps, mapStateToProps
} from "../add_tool";
import { fakeState } from "../../../__test_support__/fake_state"; import { fakeState } from "../../../__test_support__/fake_state";
import { SaveBtn } from "../../../ui"; import { SaveBtn } from "../../../ui";
import { initSave } from "../../../api/crud"; import { initSave } from "../../../api/crud";

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../../../api/crud", () => ({ edit: jest.fn() })); jest.mock("../../../api/crud", () => ({ edit: jest.fn() }));
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
@ -9,7 +7,9 @@ jest.mock("../../../history", () => ({
import * as React from "react"; import * as React from "react";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { RawEditTool as EditTool, EditToolProps, mapStateToProps } from "../edit_tool"; import {
RawEditTool as EditTool, EditToolProps, mapStateToProps
} from "../edit_tool";
import { fakeTool } from "../../../__test_support__/fake_state/resources"; import { fakeTool } from "../../../__test_support__/fake_state/resources";
import { fakeState } from "../../../__test_support__/fake_state"; import { fakeState } from "../../../__test_support__/fake_state";
import { import {

View File

@ -1,5 +1,3 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
history: { push: jest.fn() }, history: { push: jest.fn() },
getPathArray: () => "/app/designer/tools".split("/"), getPathArray: () => "/app/designer/tools".split("/"),
@ -13,7 +11,9 @@ import {
} from "../../../__test_support__/fake_state/resources"; } from "../../../__test_support__/fake_state/resources";
import { history } from "../../../history"; import { history } from "../../../history";
import { fakeState } from "../../../__test_support__/fake_state"; import { fakeState } from "../../../__test_support__/fake_state";
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder"; import {
buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
describe("<Tools />", () => { describe("<Tools />", () => {
const fakeProps = (): ToolsProps => ({ const fakeProps = (): ToolsProps => ({

View File

@ -1,11 +1,9 @@
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
const mockDevice = { execScript: jest.fn(() => Promise.resolve({})) }; const mockDevice = { execScript: jest.fn(() => Promise.resolve({})) };
jest.mock("../../device", () => ({ getDevice: () => mockDevice })); jest.mock("../../device", () => ({ getDevice: () => mockDevice }));
import * as React from "react"; import * as React from "react";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { FarmwarePage, BasicFarmwarePage } from "../index"; import { RawFarmwarePage as FarmwarePage, BasicFarmwarePage } from "../index";
import { FarmwareProps } from "../../devices/interfaces"; import { FarmwareProps } from "../../devices/interfaces";
import { import {
fakeFarmware, fakeFarmwares fakeFarmware, fakeFarmwares

View File

@ -36,7 +36,7 @@ export class CameraCalibration extends
lockOpen={process.env.NODE_ENV !== "production"}> lockOpen={process.env.NODE_ENV !== "production"}>
<button <button
onClick={this.props.dispatch(calibrate)} onClick={this.props.dispatch(calibrate)}
className="fb-button green" > className="fb-button green">
{t("Calibrate")} {t("Calibrate")}
</button> </button>
</MustBeOnline> </MustBeOnline>

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Col, BlurableInput } from "../ui/index"; import { Col, BlurableInput } from "../ui/index";
import { Pair, FarmwareConfig } from "farmbot"; import { Pair, FarmwareConfig } from "farmbot";
import { getDevice } from "../device"; import { getDevice } from "../device";
import { import {
@ -25,7 +24,8 @@ export function getConfigEnvName(farmwareName: string, configName: string) {
} }
/** Farmware description and version info for help text contents. */ /** Farmware description and version info for help text contents. */
export function farmwareHelpText(farmware: FarmwareManifestInfo | undefined): string { export function farmwareHelpText(farmware: FarmwareManifestInfo | undefined):
string {
if (farmware) { if (farmware) {
const description = farmware.meta.description; const description = farmware.meta.description;
const versionString = " (version: " + farmware.meta.version + ")"; const versionString = " (version: " + farmware.meta.version + ")";

View File

@ -37,7 +37,7 @@ const farmwareListItem = (dispatch: Function, current: string | undefined) =>
to={`/app/farmware/${urlFriendly(farmwareName)}`} to={`/app/farmware/${urlFriendly(farmwareName)}`}
key={farmwareName} key={farmwareName}
onClick={click}> onClick={click}>
<div className={`farmware-list-items ${selected}`} > <div className={`farmware-list-items ${selected}`}>
<p>{displayName}</p> <p>{displayName}</p>
</div> </div>
</Link>; </Link>;

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { ImageFlipperProps, ImageFlipperState } from "./interfaces"; import { ImageFlipperProps, ImageFlipperState } from "./interfaces";
import { Content } from "../../constants"; import { Content } from "../../constants";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";

View File

@ -2,19 +2,13 @@ const mockDevice = {
execScript: jest.fn(() => Promise.resolve()), execScript: jest.fn(() => Promise.resolve()),
setUserEnv: jest.fn(() => Promise.resolve()), setUserEnv: jest.fn(() => Promise.resolve()),
}; };
jest.mock("../../../device", () => ({ getDevice: () => mockDevice }));
jest.mock("../../../device", () => ({
getDevice: () => {
return mockDevice;
}
}));
jest.mock("react-redux", () => ({ connect: jest.fn(() => (x: {}) => x) }));
jest.mock("../../images/actions", () => ({ selectImage: jest.fn() })); jest.mock("../../images/actions", () => ({ selectImage: jest.fn() }));
import * as React from "react"; import * as React from "react";
import { mount, shallow } from "enzyme"; import { mount, shallow } from "enzyme";
import { RawWeedDetector as WeedDetector, namespace } from "../index"; import { WeedDetector, namespace } from "../index";
import { FarmwareProps } from "../../../devices/interfaces"; import { FarmwareProps } from "../../../devices/interfaces";
import { API } from "../../../api"; import { API } from "../../../api";
import { selectImage } from "../../images/actions"; import { selectImage } from "../../images/actions";

View File

@ -26,7 +26,8 @@ export class WeedDetectorConfig extends React.Component<SettingsMenuProps, {}> {
<BlurableInput type="number" <BlurableInput type="number"
id={conf} id={conf}
value={"" + envGet(conf, this.props.values)} value={"" + envGet(conf, this.props.values)}
onCommit={e => this.props.onChange(conf, parseFloat(e.currentTarget.value))} onCommit={e =>
this.props.onChange(conf, parseFloat(e.currentTarget.value))}
placeholder={label} /> placeholder={label} />
</div>; </div>;
}; };
@ -54,10 +55,12 @@ export class WeedDetectorConfig extends React.Component<SettingsMenuProps, {}> {
<input <input
type="checkbox" type="checkbox"
id="invert_hue_selection" id="invert_hue_selection"
checked={!!envGet("CAMERA_CALIBRATION_invert_hue_selection", this.props.values)} checked={!!envGet("CAMERA_CALIBRATION_invert_hue_selection",
onChange={e => this.props.onChange("CAMERA_CALIBRATION_invert_hue_selection", this.props.values)}
e.currentTarget.checked ? onChange={e =>
SPECIAL_VALUES.TRUE : SPECIAL_VALUES.FALSE)} /> this.props.onChange("CAMERA_CALIBRATION_invert_hue_selection",
e.currentTarget.checked ?
SPECIAL_VALUES.TRUE : SPECIAL_VALUES.FALSE)} />
</div> </div>
<this.NumberBox <this.NumberBox
conf={"CAMERA_CALIBRATION_calibration_object_separation"} conf={"CAMERA_CALIBRATION_calibration_object_separation"}

View File

@ -1,4 +1,3 @@
import { DropDownItem } from "../../ui/index"; import { DropDownItem } from "../../ui/index";
import { SPECIAL_VALUES } from "./remote_env/constants"; import { SPECIAL_VALUES } from "./remote_env/constants";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";

Some files were not shown because too many files have changed in this diff Show More