Merge branch 'staging' into point_meta_updates
commit
9e14c2125d
|
@ -1,6 +1,24 @@
|
|||
class BasePointSerializer < ApplicationSerializer
|
||||
attributes :device_id, :name, :pointer_type, :meta, :x, :y, :z
|
||||
|
||||
# PROBLEM:
|
||||
# * Users need a mutable way to mark a plant's creation time => `planted_at`
|
||||
# * DB Admin needs to know the _real_ created_at time.
|
||||
# * We can't change field names (or destroy data) that is in use by legacy devices
|
||||
#
|
||||
# SOLUTION:
|
||||
# * Don't allow users to modify `created_at`
|
||||
# * Provide `planted_at` if possible.
|
||||
# * Always provide `planted_at` if it is available
|
||||
# * Provide a read-only view of `created_at` if `planted_at` is `nil`
|
||||
def planted_at
|
||||
object.planted_at || object.created_at
|
||||
end
|
||||
|
||||
def created_at
|
||||
planted_at
|
||||
end
|
||||
|
||||
def meta
|
||||
object.meta || {}
|
||||
end
|
||||
|
|
|
@ -348,9 +348,6 @@ export namespace ToolTips {
|
|||
For example, you can mark a plant as "planted" during a seeding
|
||||
sequence or mark a weed as "removed" after removing it.`);
|
||||
|
||||
export const REBOOT =
|
||||
trim(`Power cycle FarmBot's onboard computer.`);
|
||||
|
||||
export const SET_SERVO_ANGLE =
|
||||
trim(`Move a servo to the provided angle. An angle of 90 degrees
|
||||
corresponds to the servo midpoint (or, for a continuous rotation
|
||||
|
@ -362,6 +359,9 @@ export namespace ToolTips {
|
|||
export const MOVE_TO_HOME =
|
||||
trim(`Move FarmBot to home for the provided axis.`);
|
||||
|
||||
export const ASSERTION =
|
||||
trim(`Evaluate Lua commands. For power users and software developers.`);
|
||||
|
||||
export const FIRMWARE_ACTION =
|
||||
trim(`FarmBot OS or micro-controller firmware action.`);
|
||||
|
||||
|
@ -740,6 +740,15 @@ export namespace Content {
|
|||
encoders, stall detection, or endstops enabled for the chosen axis.
|
||||
Enable endstops, encoders, or stall detection from the Device page for: `);
|
||||
|
||||
export const REBOOT_STEP =
|
||||
trim(`Power cycle FarmBot's onboard computer.`);
|
||||
|
||||
export const SHUTDOWN_STEP =
|
||||
trim(`Power down FarmBot's onboard computer.`);
|
||||
|
||||
export const ESTOP_STEP =
|
||||
trim(`Unlocking a device requires user intervention.`);
|
||||
|
||||
export const IN_USE =
|
||||
trim(`Used in another resource. Protected from deletion.`);
|
||||
|
||||
|
|
|
@ -1314,6 +1314,24 @@ ul {
|
|||
}
|
||||
}
|
||||
|
||||
.update-resource-step {
|
||||
.custom-meta-field {
|
||||
position: relative;
|
||||
.fa-undo {
|
||||
position: absolute;
|
||||
top: 0.65rem;
|
||||
right: 0.5rem;
|
||||
color: $medium_light_gray;
|
||||
&:hover {
|
||||
color: $dark_gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
.update-resource-pair {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.farmware-name-manual-input {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
|
|
@ -136,6 +136,9 @@
|
|||
&.reboot-step {
|
||||
background: $brown;
|
||||
}
|
||||
&.shutdown-step {
|
||||
background: $brown;
|
||||
}
|
||||
&.unknown-step {
|
||||
background: $gray;
|
||||
}
|
||||
|
@ -253,6 +256,9 @@
|
|||
&.emergency-stop-step {
|
||||
background: $light_red;
|
||||
}
|
||||
&.shutdown-step {
|
||||
background: $light_brown;
|
||||
}
|
||||
&.reboot-step {
|
||||
background: $light_brown;
|
||||
}
|
||||
|
|
|
@ -11,12 +11,15 @@ import { OsUpdateButton } from "../os_update_button";
|
|||
import { OsUpdateButtonProps } from "../interfaces";
|
||||
import { ShouldDisplay } from "../../../interfaces";
|
||||
import { Content } from "../../../../constants";
|
||||
import { ConfigurationName } from "farmbot";
|
||||
|
||||
const UPDATE_CHANNEL = "update_channel" as ConfigurationName;
|
||||
|
||||
describe("<OsUpdateButton/>", () => {
|
||||
beforeEach(() => {
|
||||
bot.currentOSVersion = "6.1.6";
|
||||
bot.hardware.informational_settings.controller_version = "6.1.6";
|
||||
bot.hardware.configuration.beta_opt_in = false;
|
||||
(bot.hardware.configuration[UPDATE_CHANNEL] as string) = "stable";
|
||||
});
|
||||
|
||||
const fakeProps = (): OsUpdateButtonProps => ({
|
||||
|
@ -33,7 +36,6 @@ describe("<OsUpdateButton/>", () => {
|
|||
availableVersion: string | undefined;
|
||||
availableBetaVersion: string | undefined;
|
||||
availableBetaCommit: string | undefined;
|
||||
betaOptIn: boolean | undefined;
|
||||
onBeta: boolean | undefined;
|
||||
update_available?: boolean | undefined;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
|
@ -46,7 +48,6 @@ describe("<OsUpdateButton/>", () => {
|
|||
availableVersion: "6.1.6",
|
||||
availableBetaVersion: undefined,
|
||||
availableBetaCommit: undefined,
|
||||
betaOptIn: false,
|
||||
onBeta: false,
|
||||
shouldDisplay: () => false,
|
||||
update_channel: "stable",
|
||||
|
@ -104,7 +105,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
expected: Results) => {
|
||||
const {
|
||||
installedVersion, installedCommit, onBeta, update_available,
|
||||
availableVersion, availableBetaVersion, availableBetaCommit, betaOptIn,
|
||||
availableVersion, availableBetaVersion, availableBetaCommit,
|
||||
shouldDisplay, update_channel,
|
||||
} = testProps;
|
||||
bot.hardware.informational_settings.controller_version = installedVersion;
|
||||
|
@ -115,9 +116,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
bot.currentOSVersion = availableVersion;
|
||||
bot.currentBetaOSVersion = availableBetaVersion;
|
||||
bot.currentBetaOSCommit = availableBetaCommit;
|
||||
bot.hardware.configuration.beta_opt_in = betaOptIn;
|
||||
// tslint:disable-next-line:no-any
|
||||
(bot.hardware.configuration as any).update_channel = update_channel;
|
||||
(bot.hardware.configuration[UPDATE_CHANNEL] as string) = update_channel;
|
||||
|
||||
const p = fakeProps();
|
||||
p.shouldDisplay = shouldDisplay;
|
||||
|
@ -156,7 +155,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableVersion = undefined;
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
const expectedResults = cantConnect("release server");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -166,7 +165,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableVersion = undefined;
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
const expectedResults = updateNeeded("6.1.7-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -175,7 +174,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableBetaVersion = undefined;
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
const expectedResults = upToDate("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -205,7 +204,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.5";
|
||||
testProps.availableBetaVersion = "7.0.0-beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
const expectedResults = updateNeeded("7.0.0-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -214,7 +213,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableBetaVersion = "6.1.6-beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
const expectedResults = upToDate("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -223,7 +222,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableBetaVersion = "6.1.6-beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
testProps.onBeta = true;
|
||||
const expectedResults = updateNeeded("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
|
@ -233,7 +232,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.availableBetaVersion = "6.1.6-beta";
|
||||
testProps.betaOptIn = false;
|
||||
testProps.update_channel = "stable";
|
||||
testProps.onBeta = true;
|
||||
const expectedResults = updateNeeded("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
|
@ -243,7 +242,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.7";
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
testProps.onBeta = true;
|
||||
const expectedResults = upToDate("6.1.7-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
|
@ -253,7 +252,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.7-beta";
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
const expectedResults = upToDate("6.1.7-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
@ -264,7 +263,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
testProps.installedCommit = "old commit";
|
||||
testProps.availableBetaVersion = "7.0.0-beta";
|
||||
testProps.availableBetaCommit = "new commit";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
testProps.onBeta = true;
|
||||
const expectedResults = updateNeeded("7.0.0-beta");
|
||||
testButtonState(testProps, expectedResults);
|
||||
|
@ -273,7 +272,7 @@ describe("<OsUpdateButton/>", () => {
|
|||
it("handles installed version newer than available (beta enabled)", () => {
|
||||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.7";
|
||||
testProps.betaOptIn = true;
|
||||
testProps.update_channel = "beta";
|
||||
testProps.onBeta = false;
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
const expectedResults = upToDate("6.1.7-beta");
|
||||
|
@ -308,16 +307,6 @@ describe("<OsUpdateButton/>", () => {
|
|||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
||||
it("doesn't use update_channel value", () => {
|
||||
const testProps = defaultTestProps();
|
||||
testProps.installedVersion = "6.1.6";
|
||||
testProps.shouldDisplay = () => false;
|
||||
testProps.update_channel = "beta";
|
||||
testProps.availableBetaVersion = "6.1.7-beta";
|
||||
const expectedResults = upToDate("6.1.6");
|
||||
testButtonState(testProps, expectedResults);
|
||||
});
|
||||
|
||||
it("compares release candidates: newer", () => {
|
||||
const testProps = defaultTestProps();
|
||||
testProps.availableVersion = "6.1.5";
|
||||
|
|
|
@ -263,7 +263,7 @@ export function FbosDetails(props: FbosDetailsProps) {
|
|||
soc_temp, wifi_level, uptime, memory_usage, disk_usage, throttled,
|
||||
wifi_level_percent, cpu_usage, private_ip,
|
||||
} = props.botInfoSettings;
|
||||
const { last_ota, last_ota_checkup } = props.deviceAccount.body;
|
||||
const { last_ota, last_ota_checkup, fbos_version } = props.deviceAccount.body;
|
||||
const infoFwCommit = firmware_version?.includes(".") ? firmware_commit : "---";
|
||||
const firmwareCommit = firmware_version?.split("-")[1] || infoFwCommit;
|
||||
|
||||
|
@ -273,6 +273,7 @@ export function FbosDetails(props: FbosDetailsProps) {
|
|||
botToMqttLastSeen={props.botToMqttLastSeen}
|
||||
timeSettings={props.timeSettings}
|
||||
device={props.deviceAccount} />
|
||||
<p><b>{t("Version last seen")}: </b>{fbos_version}</p>
|
||||
<p><b>{t("Environment")}: </b>{env}</p>
|
||||
<CommitDisplay title={t("Commit")}
|
||||
repo={FarmBotRepo.FarmBotOS} commit={commit} />
|
||||
|
|
|
@ -4,7 +4,7 @@ import { SemverResult, semverCompare } from "../../../util";
|
|||
import { OsUpdateButtonProps } from "./interfaces";
|
||||
import { checkControllerUpdates } from "../../actions";
|
||||
import { isString } from "lodash";
|
||||
import { BotState, Feature } from "../../interfaces";
|
||||
import { BotState } from "../../interfaces";
|
||||
import { Content } from "../../../constants";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
|
||||
|
@ -154,9 +154,8 @@ export const OsUpdateButton = (props: OsUpdateButtonProps) => {
|
|||
const { controller_version } = bot.hardware.informational_settings;
|
||||
|
||||
/** FBOS beta release opt-in setting. */
|
||||
const betaOptIn = props.shouldDisplay(Feature.use_update_channel)
|
||||
? sourceFbosConfig("update_channel" as ConfigurationName).value !== "stable"
|
||||
: !!sourceFbosConfig("beta_opt_in").value;
|
||||
const betaOptIn =
|
||||
sourceFbosConfig("update_channel" as ConfigurationName).value !== "stable";
|
||||
/** FBOS update availability. */
|
||||
const buttonStatusProps = buttonVersionStatus({ bot, betaOptIn });
|
||||
|
||||
|
|
|
@ -117,7 +117,6 @@ describe("<Logs />", () => {
|
|||
|
||||
it("shows filtered overall filter status", () => {
|
||||
const p = fakeProps();
|
||||
p.shouldDisplay = () => true;
|
||||
const wrapper = mount(<Logs {...p} />);
|
||||
const state = fakeLogsState();
|
||||
state.assertion = 2;
|
||||
|
@ -129,10 +128,9 @@ describe("<Logs />", () => {
|
|||
|
||||
it("shows unfiltered overall filter status", () => {
|
||||
const p = fakeProps();
|
||||
p.shouldDisplay = () => false;
|
||||
const wrapper = mount(<Logs {...p} />);
|
||||
const state = fakeLogsState();
|
||||
state.assertion = 2;
|
||||
state.assertion = 3;
|
||||
wrapper.setState(state);
|
||||
const filterBtn = wrapper.find("button").first();
|
||||
expect(filterBtn.text().toLowerCase()).toEqual("filter");
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Filters } from "../interfaces";
|
|||
import { startCase } from "lodash";
|
||||
import { MESSAGE_TYPES, MessageType } from "../../sequences/interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { Feature, ShouldDisplay } from "../../devices/interfaces";
|
||||
|
||||
const MENU_ORDER: string[] = [
|
||||
MessageType.success,
|
||||
|
@ -26,11 +25,9 @@ const menuSort = (a: string, b: string) =>
|
|||
|
||||
/** Get log filter keys from LogsState. */
|
||||
export const filterStateKeys =
|
||||
(state: LogsState, shouldDisplay: ShouldDisplay) =>
|
||||
(state: LogsState) =>
|
||||
Object.keys(state)
|
||||
.filter(key => !["autoscroll", "markdown", "searchTerm"].includes(key))
|
||||
.filter(key => shouldDisplay(Feature.assertion_block)
|
||||
|| key !== "assertion");
|
||||
.filter(key => !["autoscroll", "markdown", "searchTerm"].includes(key));
|
||||
|
||||
export const LogsFilterMenu = (props: LogsFilterMenuProps) => {
|
||||
/** Filter level 0: logs hidden. */
|
||||
|
@ -56,7 +53,7 @@ export const LogsFilterMenu = (props: LogsFilterMenuProps) => {
|
|||
{t("normal")}
|
||||
</button>
|
||||
</fieldset>
|
||||
{filterStateKeys(props.state, props.shouldDisplay).sort(menuSort)
|
||||
{filterStateKeys(props.state).sort(menuSort)
|
||||
.map((logType: keyof Filters) =>
|
||||
<fieldset key={logType}>
|
||||
<label>
|
||||
|
|
|
@ -76,7 +76,7 @@ export class RawLogs extends React.Component<LogsProps, Partial<LogsState>> {
|
|||
|
||||
/** Determine if log type filters are active. */
|
||||
get filterActive() {
|
||||
const filterKeys = filterStateKeys(this.state, this.props.shouldDisplay);
|
||||
const filterKeys = filterStateKeys(this.state);
|
||||
const filterValues = filterKeys
|
||||
.map((key: keyof Filters) => this.state[key]);
|
||||
// Filters active if every log type level is not equal to 3 (max verbosity)
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
import { regimensReducer, RegimenState } from "../reducer";
|
||||
import { regimensReducer, RegimenState, newWeek } from "../reducer";
|
||||
import { Actions } from "../../constants";
|
||||
import { popWeek, pushWeek, selectDays, deselectDays } from "../bulk_scheduler/actions";
|
||||
import {
|
||||
popWeek, pushWeek, selectDays, deselectDays,
|
||||
} from "../bulk_scheduler/actions";
|
||||
import { defensiveClone } from "../../util";
|
||||
import { Week } from "../bulk_scheduler/interfaces";
|
||||
|
||||
const week = newWeek();
|
||||
Object.entries(week.days).map(([day, _]: [keyof Week["days"], boolean]) => {
|
||||
week.days[day] = true;
|
||||
});
|
||||
week.days.day7 = false;
|
||||
|
||||
const STATE: RegimenState = {
|
||||
dailyOffsetMs: 300000,
|
||||
selectedSequenceUUID: "Sequence.71.167",
|
||||
currentRegimen: "Regimen.4.56",
|
||||
weeks: [
|
||||
{
|
||||
"days": {
|
||||
"day1": true,
|
||||
"day2": true,
|
||||
"day3": true,
|
||||
"day4": true,
|
||||
"day5": true,
|
||||
"day6": true,
|
||||
"day7": false
|
||||
}
|
||||
},
|
||||
],
|
||||
weeks: [week],
|
||||
schedulerOpen: false,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { render } from "enzyme";
|
||||
import { WeekRow } from "../bulk_scheduler/week_row";
|
||||
import { WeekRowProps } from "../bulk_scheduler/interfaces";
|
||||
import { betterMerge } from "../../util";
|
||||
|
||||
function weekProps(p?: Partial<WeekRowProps>): WeekRowProps {
|
||||
return betterMerge({
|
||||
dispatch: jest.fn(),
|
||||
index: 0,
|
||||
week: {
|
||||
"days": {
|
||||
"day1": false,
|
||||
"day2": false,
|
||||
"day3": false,
|
||||
"day4": false,
|
||||
"day5": false,
|
||||
"day6": false,
|
||||
"day7": false
|
||||
}
|
||||
}
|
||||
}, p || {});
|
||||
}
|
||||
|
||||
describe("<WeekRow/>", () => {
|
||||
it("renders week 1 day numbers", () => {
|
||||
const wrapper = render(<WeekRow {...weekProps() } />);
|
||||
const txt = wrapper.text();
|
||||
expect(txt).toEqual("Week 11234567");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<WeekRow/>", () => {
|
||||
it("renders week 2 day numbers", () => {
|
||||
const wrapper = render(<WeekRow {...weekProps({ index: 1 }) } />);
|
||||
const txt = wrapper.text();
|
||||
expect(txt).toEqual("Week 2891011121314");
|
||||
});
|
||||
});
|
|
@ -19,6 +19,7 @@ import { arrayUnwrap } from "../../../resources/util";
|
|||
import { overwrite } from "../../../api/crud";
|
||||
import { fakeVariableNameSet } from "../../../__test_support__/fake_variables";
|
||||
import { error, warning } from "../../../toast/toast";
|
||||
import { newWeek } from "../../reducer";
|
||||
|
||||
const sequence_id = 23;
|
||||
const regimen_id = 32;
|
||||
|
@ -53,18 +54,9 @@ describe("commitBulkEditor()", () => {
|
|||
state.resources.consumers.regimens.currentRegimen = regimenUuid;
|
||||
state.resources.consumers.regimens.selectedSequenceUUID = sequenceUuid;
|
||||
state.resources.consumers.regimens.dailyOffsetMs = 2000;
|
||||
state.resources.consumers.regimens.weeks = [{
|
||||
days:
|
||||
{
|
||||
day1: true,
|
||||
day2: false,
|
||||
day3: false,
|
||||
day4: false,
|
||||
day5: false,
|
||||
day6: false,
|
||||
day7: false
|
||||
}
|
||||
}];
|
||||
const week = newWeek();
|
||||
week.days.day1 = true;
|
||||
state.resources.consumers.regimens.weeks = [week];
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { groupRegimenItemsByWeek } from "../group_regimen_items_by_week";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import { newWeek } from "../../reducer";
|
||||
|
||||
describe("groupRegimenItemsByWeek()", () => {
|
||||
it("groups regimen items by week", () => {
|
||||
const sequence = fakeSequence();
|
||||
sequence.body.id = 1;
|
||||
const week1 = newWeek();
|
||||
week1.days.day1 = true;
|
||||
const week2 = newWeek();
|
||||
const week3 = newWeek();
|
||||
week3.days.day2 = true;
|
||||
week3.days.day4 = true;
|
||||
const { day1, day2, day3, day4, day5, day6, day7 } = week3.days;
|
||||
week3.days = { day1, day4, day3, day2, day5, day6, day7 };
|
||||
const weeks = [week1, week2, week3];
|
||||
const result = groupRegimenItemsByWeek(weeks, 100, sequence.body);
|
||||
expect(result).toEqual([
|
||||
{ time_offset: 100, sequence_id: 1 },
|
||||
{ time_offset: 1296000100, sequence_id: 1 },
|
||||
{ time_offset: 1468800100, sequence_id: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles missing sequence id", () => {
|
||||
const sequence = fakeSequence();
|
||||
sequence.body.id = undefined;
|
||||
const week = newWeek();
|
||||
week.days.day1 = true;
|
||||
const result = groupRegimenItemsByWeek([week], 0, sequence.body);
|
||||
expect(result).toEqual([
|
||||
{ time_offset: 0, sequence_id: -1 },
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -8,20 +8,12 @@ import {
|
|||
import { Actions } from "../../../constants";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import { AddButton } from "../add_button";
|
||||
import { newWeek } from "../../reducer";
|
||||
|
||||
describe("<BulkScheduler />", () => {
|
||||
const weeks = [{
|
||||
days:
|
||||
{
|
||||
day1: true,
|
||||
day2: false,
|
||||
day3: false,
|
||||
day4: false,
|
||||
day5: false,
|
||||
day6: false,
|
||||
day7: false
|
||||
}
|
||||
}];
|
||||
const week = newWeek();
|
||||
week.days.day1 = true;
|
||||
const weeks = [week];
|
||||
|
||||
function fakeProps(): BulkEditorProps {
|
||||
const sequence = fakeSequence();
|
||||
|
|
|
@ -3,20 +3,12 @@ import { mount } from "enzyme";
|
|||
import { WeekGrid } from "../week_grid";
|
||||
import { WeekGridProps } from "../interfaces";
|
||||
import { Actions } from "../../../constants";
|
||||
import { newWeek } from "../../reducer";
|
||||
|
||||
describe("<WeekGrid />", () => {
|
||||
const weeks = [{
|
||||
days:
|
||||
{
|
||||
day1: true,
|
||||
day2: false,
|
||||
day3: false,
|
||||
day4: false,
|
||||
day5: false,
|
||||
day6: false,
|
||||
day7: false
|
||||
}
|
||||
}];
|
||||
const week = newWeek();
|
||||
week.days.day1 = true;
|
||||
const weeks = [week];
|
||||
|
||||
it("renders", () => {
|
||||
const props: WeekGridProps = { weeks, dispatch: jest.fn() };
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import * as React from "react";
|
||||
import { render, mount } from "enzyme";
|
||||
import { WeekRow } from "../week_row";
|
||||
import { WeekRowProps } from "../interfaces";
|
||||
import { betterMerge } from "../../../util";
|
||||
import { newWeek } from "../../reducer";
|
||||
import { Actions } from "../../../constants";
|
||||
|
||||
describe("<WeekRow/>", () => {
|
||||
const fakeProps = (p?: Partial<WeekRowProps>): WeekRowProps =>
|
||||
betterMerge({
|
||||
dispatch: jest.fn(),
|
||||
index: 0,
|
||||
week: newWeek()
|
||||
}, p || {});
|
||||
|
||||
it("renders week 1 day numbers", () => {
|
||||
const wrapper = render(<WeekRow {...fakeProps()} />);
|
||||
expect(wrapper.text()).toEqual("Week 11234567");
|
||||
});
|
||||
|
||||
it("renders week 2 day numbers", () => {
|
||||
const wrapper = render(<WeekRow {...fakeProps({ index: 1 })} />);
|
||||
expect(wrapper.text()).toEqual("Week 2891011121314");
|
||||
});
|
||||
|
||||
it("selects day", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<WeekRow {...p} />);
|
||||
wrapper.find("input").first().simulate("click");
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.TOGGLE_DAY,
|
||||
payload: { week: 0, day: 1 },
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,8 +6,7 @@ import { t } from "../../i18next_wrapper";
|
|||
export function WeekRow({ index, dispatch, week }: WeekRowProps) {
|
||||
return <div className="week-row">
|
||||
<label className="week-label">{t("Week")} {index + 1}</label>
|
||||
{
|
||||
DAYS.map(function (day, i) {
|
||||
{DAYS.map(function (day, i) {
|
||||
const id = `${index}-${day}`;
|
||||
return <Day day={i + 1}
|
||||
week={index}
|
||||
|
@ -15,8 +14,7 @@ export function WeekRow({ index, dispatch, week }: WeekRowProps) {
|
|||
id={id}
|
||||
key={id}
|
||||
active={week.days[day]} />;
|
||||
})
|
||||
}
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ export interface RegimenState {
|
|||
schedulerOpen: boolean;
|
||||
}
|
||||
|
||||
function newWeek() {
|
||||
export function newWeek(): Week {
|
||||
return {
|
||||
days: {
|
||||
day1: false,
|
||||
|
|
|
@ -60,7 +60,7 @@ export const determineVector =
|
|||
};
|
||||
|
||||
/** Try to find a vector in scope declarations for the variable. */
|
||||
const maybeFindVariable = (
|
||||
export const maybeFindVariable = (
|
||||
label: string, resources: ResourceIndex, uuid?: UUID,
|
||||
): SequenceMeta | undefined =>
|
||||
uuid ? findVariableByName(resources, uuid, label) : undefined;
|
||||
|
|
|
@ -33,7 +33,8 @@ describe("<StepButtonCluster />", () => {
|
|||
it("has correct drag data", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<StepButtonCluster {...p} />);
|
||||
const stepButton = wrapper.find("div").last();
|
||||
const steps = wrapper.find(".step-dragger");
|
||||
const stepButton = steps.at(steps.length - 2);
|
||||
expect(stepButton.text().toLowerCase()).toEqual("take photo");
|
||||
stepButton.simulate("dragStart", { dataTransfer: { setData: jest.fn() } });
|
||||
expect(p.dispatch).toHaveBeenCalledWith(expect.objectContaining({
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
determineVector, determineDropdown, SequenceMeta, determineVarDDILabel,
|
||||
} from "../../resources/sequence_meta";
|
||||
import { ResourceIndex, UUID } from "../../resources/interfaces";
|
||||
import { Feature } from "../../devices/interfaces";
|
||||
import { DefaultValueForm } from "./default_value_form";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { CoordinateInputBoxes } from "./location_form_coordinate_input_boxes";
|
||||
|
@ -49,13 +48,12 @@ export const LocationForm =
|
|||
const { celeryNode, dropdown, vector } = maybeUseStepData({
|
||||
resources, bodyVariables, variable, uuid: sequenceUuid
|
||||
});
|
||||
const displayVariables = props.shouldDisplay(Feature.variables) &&
|
||||
allowedVariableNodes !== AllowedVariableNodes.variable;
|
||||
const displayVariables = allowedVariableNodes !== AllowedVariableNodes.variable;
|
||||
const headerForm = allowedVariableNodes === AllowedVariableNodes.parameter;
|
||||
const variableListItems = displayVariables ? [PARENT(determineVarDDILabel({
|
||||
label: "parent", resources, uuid: sequenceUuid, forceExternal: headerForm
|
||||
}))] : [];
|
||||
const displayGroups = props.shouldDisplay(Feature.groups) && !hideGroups;
|
||||
const displayGroups = !hideGroups;
|
||||
const unfiltered = locationFormList(resources, variableListItems, displayGroups);
|
||||
const list = props.customFilterRule ?
|
||||
unfiltered.filter(props.customFilterRule) : unfiltered;
|
||||
|
|
|
@ -95,6 +95,11 @@ export function StepButtonCluster(props: StepButtonProps) {
|
|||
color="brown">
|
||||
{t("REBOOT")}
|
||||
</StepButton>,
|
||||
<StepButton {...commonStepProps}
|
||||
step={{ kind: "power_off", args: {} }}
|
||||
color="brown">
|
||||
{t("SHUTDOWN")}
|
||||
</StepButton>,
|
||||
<StepButton {...commonStepProps}
|
||||
step={{ kind: "emergency_lock", args: {} }}
|
||||
color="red">
|
||||
|
@ -163,9 +168,7 @@ export function StepButtonCluster(props: StepButtonProps) {
|
|||
step={{ kind: "take_photo", args: {} }}>
|
||||
{t("TAKE PHOTO")}
|
||||
</StepButton>,
|
||||
];
|
||||
|
||||
shouldDisplay(Feature.assertion_block) && ALL_THE_BUTTONS.push(<StepButton
|
||||
<StepButton
|
||||
{...commonStepProps}
|
||||
step={{
|
||||
kind: "assertion",
|
||||
|
@ -177,25 +180,20 @@ export function StepButtonCluster(props: StepButtonProps) {
|
|||
}}
|
||||
color="purple">
|
||||
{t("ASSERTION")}
|
||||
</StepButton>);
|
||||
</StepButton>,
|
||||
];
|
||||
|
||||
shouldDisplay(Feature.update_resource) && ALL_THE_BUTTONS.push(<StepButton
|
||||
{...commonStepProps}
|
||||
step={{
|
||||
kind: "update_resource",
|
||||
args: {
|
||||
resource: {
|
||||
kind: "resource",
|
||||
args: { resource_id: 0, resource_type: "Device" }
|
||||
}
|
||||
},
|
||||
body: [
|
||||
{ kind: "pair", args: { label: "mounted_tool_id", value: 0 } },
|
||||
],
|
||||
args: { resource: NOTHING_SELECTED },
|
||||
body: [],
|
||||
}}
|
||||
color="brown">
|
||||
{t("Mark As...")}
|
||||
</StepButton>);
|
||||
|
||||
return <Row>
|
||||
<div className="step-button-cluster">
|
||||
{ALL_THE_BUTTONS.map((stepButton, inx) =>
|
||||
|
|
|
@ -3,12 +3,16 @@ jest.mock("../../../api/crud", () => ({
|
|||
}));
|
||||
|
||||
import { remove, move, splice, renderCeleryNode } from "../index";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
fakeSequence, fakePlant,
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { overwrite } from "../../../api/crud";
|
||||
import { SequenceBodyItem, Wait } from "farmbot";
|
||||
import { mount } from "enzyme";
|
||||
import { StepParams, MessageType } from "../../interfaces";
|
||||
import { emptyState } from "../../../resources/reducer";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("remove()", () => {
|
||||
const fakeProps = () => ({
|
||||
|
@ -82,12 +86,15 @@ describe("splice()", () => {
|
|||
describe("renderCeleryNode()", () => {
|
||||
const currentStep: Wait = { kind: "wait", args: { milliseconds: 100 } };
|
||||
|
||||
const plant = fakePlant();
|
||||
plant.body.id = 23;
|
||||
|
||||
const fakeProps = (): StepParams => ({
|
||||
currentSequence: fakeSequence(),
|
||||
currentStep: currentStep,
|
||||
dispatch: jest.fn(),
|
||||
index: 0,
|
||||
resources: emptyState().index,
|
||||
resources: buildResourceIndex([plant]).index,
|
||||
confirmStepDeletion: false,
|
||||
});
|
||||
|
||||
|
@ -154,13 +161,13 @@ describe("renderCeleryNode()", () => {
|
|||
resource: {
|
||||
kind: "resource",
|
||||
args: { resource_id: 23, resource_type: "Plant" }
|
||||
}
|
||||
},
|
||||
body: [
|
||||
{ kind: "pair", args: { label: "plant_stage", value: "planted" } },
|
||||
]
|
||||
}
|
||||
},
|
||||
expected: "markplant 23 as"
|
||||
expected: "markstrawberry plant 1 (100, 200, 0)fieldplant stageasplanted"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
|
|
|
@ -2,11 +2,12 @@ import * as React from "react";
|
|||
import { TileIf } from "../tile_if";
|
||||
import { mount } from "enzyme";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import { If } from "farmbot/dist";
|
||||
import { If, Wait } from "farmbot/dist";
|
||||
import { emptyState } from "../../../resources/reducer";
|
||||
import { StepParams } from "../../interfaces";
|
||||
|
||||
describe("<TileIf/>", () => {
|
||||
function bootstrapTest() {
|
||||
describe("<TileIf />", () => {
|
||||
const fakeProps = (): StepParams => {
|
||||
const currentStep: If = {
|
||||
kind: "_if",
|
||||
args: {
|
||||
|
@ -18,35 +19,27 @@ describe("<TileIf/>", () => {
|
|||
}
|
||||
};
|
||||
return {
|
||||
component: mount(<TileIf
|
||||
currentSequence={fakeSequence()}
|
||||
currentStep={currentStep}
|
||||
dispatch={jest.fn()}
|
||||
index={0}
|
||||
resources={emptyState().index}
|
||||
confirmStepDeletion={false}
|
||||
showPins={true} />)
|
||||
currentSequence: fakeSequence(),
|
||||
currentStep: currentStep,
|
||||
dispatch: jest.fn(),
|
||||
index: 0,
|
||||
resources: emptyState().index,
|
||||
confirmStepDeletion: false,
|
||||
showPins: true,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
it("renders inputs", () => {
|
||||
const block = bootstrapTest().component;
|
||||
const inputs = block.find("input");
|
||||
const labels = block.find("label");
|
||||
const buttons = block.find("button");
|
||||
expect(inputs.length).toEqual(2);
|
||||
expect(labels.length).toEqual(5);
|
||||
expect(buttons.length).toEqual(4);
|
||||
expect(inputs.first().props().placeholder).toEqual("If ...");
|
||||
expect(labels.at(0).text()).toEqual("Variable");
|
||||
expect(buttons.at(0).text()).toEqual("Pin 0");
|
||||
expect(labels.at(1).text()).toEqual("Operator");
|
||||
expect(buttons.at(1).text()).toEqual("is");
|
||||
expect(labels.at(2).text()).toEqual("Value");
|
||||
expect(inputs.at(1).props().value).toEqual(0);
|
||||
expect(labels.at(3).text()).toEqual("Then Execute");
|
||||
expect(buttons.at(2).text()).toEqual("None");
|
||||
expect(labels.at(4).text()).toEqual("Else Execute");
|
||||
expect(buttons.at(3).text()).toEqual("None");
|
||||
it("renders if step", () => {
|
||||
const wrapper = mount(<TileIf {...fakeProps()} />);
|
||||
["Variable", "Operator", "Value", "Then Execute", "Else Execute"]
|
||||
.map(string => expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
||||
it("doesn't render if step", () => {
|
||||
const p = fakeProps();
|
||||
const waitStep: Wait = { kind: "wait", args: { milliseconds: 0 } };
|
||||
p.currentStep = waitStep;
|
||||
const wrapper = mount(<TileIf {...p} />);
|
||||
expect(wrapper.text()).toEqual("Expected `_if` node");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import * as React from "react";
|
||||
import { TileMarkAs } from "../tile_mark_as";
|
||||
import { mount } from "enzyme";
|
||||
import {
|
||||
fakeSequence, fakePlant,
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { UpdateResource, Wait } from "farmbot/dist";
|
||||
import { StepParams } from "../../interfaces";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("<TileMarkAs />", () => {
|
||||
const fakeProps = (): StepParams => {
|
||||
const currentStep: UpdateResource = {
|
||||
kind: "update_resource",
|
||||
args: {
|
||||
resource: {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 1 }
|
||||
},
|
||||
},
|
||||
body: [
|
||||
{ kind: "pair", args: { label: "some_attr", value: "some_value" } },
|
||||
],
|
||||
};
|
||||
const plant = fakePlant();
|
||||
plant.body.id = 1;
|
||||
return {
|
||||
currentSequence: fakeSequence(),
|
||||
currentStep: currentStep,
|
||||
dispatch: jest.fn(),
|
||||
index: 0,
|
||||
resources: buildResourceIndex([plant]).index,
|
||||
confirmStepDeletion: false,
|
||||
};
|
||||
};
|
||||
|
||||
it("renders if step", () => {
|
||||
const wrapper = mount(<TileMarkAs {...fakeProps()} />);
|
||||
["Mark", "Strawberry plant 1 (100, 200, 0)", "field", "as"]
|
||||
.map(string => expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
||||
it("doesn't render update_resource step", () => {
|
||||
const p = fakeProps();
|
||||
const waitStep: Wait = { kind: "wait", args: { milliseconds: 0 } };
|
||||
p.currentStep = waitStep;
|
||||
const wrapper = mount(<TileMarkAs {...p} />);
|
||||
expect(wrapper.text()).toEqual("Expected `update_resource` node");
|
||||
});
|
||||
});
|
|
@ -5,16 +5,15 @@ import { render } from "enzyme";
|
|||
import React from "react";
|
||||
import { StepParams } from "../../interfaces";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { editStep } from "../../../api/crud";
|
||||
import { Reboot } from "farmbot";
|
||||
import { Content } from "../../../constants";
|
||||
|
||||
const fakeProps = (): StepParams => {
|
||||
const currentSequence = fakeSequence();
|
||||
const resources = buildResourceIndex().index;
|
||||
|
||||
return {
|
||||
currentSequence,
|
||||
const fakeProps = (): StepParams => ({
|
||||
currentSequence: fakeSequence(),
|
||||
currentStep: {
|
||||
kind: "reboot",
|
||||
args: {
|
||||
|
@ -23,41 +22,39 @@ const fakeProps = (): StepParams => {
|
|||
},
|
||||
dispatch: jest.fn(),
|
||||
index: 1,
|
||||
resources,
|
||||
resources: buildResourceIndex().index,
|
||||
confirmStepDeletion: false,
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
describe("<TileReboot/>", () => {
|
||||
it("renders", () => {
|
||||
const el = render(<TileReboot {...fakeProps()} />);
|
||||
const verbiage = el.text();
|
||||
expect(verbiage).toContain("Power cycle FarmBot's onboard computer.");
|
||||
const block = render(<TileReboot {...fakeProps()} />);
|
||||
expect(block.text()).toContain(Content.REBOOT_STEP);
|
||||
});
|
||||
|
||||
it("crashes if the step is of the wrong `kind`", () => {
|
||||
const props = fakeProps();
|
||||
props.currentStep = { kind: "sync", args: {} };
|
||||
const boom = () => TileReboot(props);
|
||||
const p = fakeProps();
|
||||
p.currentStep = { kind: "sync", args: {} };
|
||||
const boom = () => TileReboot(p);
|
||||
expect(boom).toThrowError();
|
||||
});
|
||||
|
||||
it("edits the reboot step", () => {
|
||||
const props = fakeProps();
|
||||
const editFn = editTheRebootStep(props);
|
||||
const p = fakeProps();
|
||||
const editFn = editTheRebootStep(p);
|
||||
editFn("arduino_firmware");
|
||||
expect(props.dispatch).toHaveBeenCalled();
|
||||
expect(p.dispatch).toHaveBeenCalled();
|
||||
expect(editStep).toHaveBeenCalledWith({
|
||||
step: props.currentStep,
|
||||
index: props.index,
|
||||
sequence: props.currentSequence,
|
||||
step: p.currentStep,
|
||||
index: p.index,
|
||||
sequence: p.currentSequence,
|
||||
executor: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it("executes the executor", () => {
|
||||
const props = fakeProps();
|
||||
const step = props.currentStep as Reboot;
|
||||
const p = fakeProps();
|
||||
const step = p.currentStep as Reboot;
|
||||
step.args.package = "X";
|
||||
const fn = rebootExecutor("arduino_firmware");
|
||||
fn(step);
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { TileShutdown } from "../tile_shutdown";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import { PowerOff } from "farmbot";
|
||||
import { emptyState } from "../../../resources/reducer";
|
||||
import { StepParams } from "../../interfaces";
|
||||
import { Content } from "../../../constants";
|
||||
|
||||
describe("<TileShutdown />", () => {
|
||||
const currentStep: PowerOff = {
|
||||
kind: "power_off",
|
||||
args: {},
|
||||
};
|
||||
|
||||
const fakeProps = (): StepParams => ({
|
||||
currentSequence: fakeSequence(),
|
||||
currentStep: currentStep,
|
||||
dispatch: jest.fn(),
|
||||
index: 0,
|
||||
resources: emptyState().index,
|
||||
confirmStepDeletion: false,
|
||||
});
|
||||
|
||||
it("renders step", () => {
|
||||
const block = mount(<TileShutdown {...fakeProps()} />);
|
||||
expect(block.text()).toContain(Content.SHUTDOWN_STEP);
|
||||
});
|
||||
});
|
|
@ -18,7 +18,7 @@ import { TileExecuteScript } from "./tile_execute_script";
|
|||
import { TileTakePhoto } from "./tile_take_photo";
|
||||
import { overwrite } from "../../api/crud";
|
||||
import { TileFindHome } from "./tile_find_home";
|
||||
import { MarkAs } from "./mark_as";
|
||||
import { TileMarkAs } from "./tile_mark_as";
|
||||
import { TileUnknown } from "./tile_unknown";
|
||||
import { forceSetStepTag } from "../../resources/sequence_tagging";
|
||||
import { compact, assign } from "lodash";
|
||||
|
@ -34,6 +34,7 @@ import { TileAssertion } from "./tile_assertion";
|
|||
import { TileEmergencyStop } from "./tile_emergency_stop";
|
||||
import { TileReboot } from "./tile_reboot";
|
||||
import { TileOldMarkAs } from "./tile_old_mark_as";
|
||||
import { TileShutdown } from "./tile_shutdown";
|
||||
|
||||
interface MoveParams {
|
||||
step: Step;
|
||||
|
@ -150,7 +151,7 @@ export function renderCeleryNode(props: StepParams) {
|
|||
case "take_photo": return <TileTakePhoto {...props} />;
|
||||
case "wait": return <TileWait {...props} />;
|
||||
case "write_pin": return <TileWritePin {...props} />;
|
||||
case "update_resource": return <MarkAs {...props} />;
|
||||
case "update_resource": return <TileMarkAs {...props} />;
|
||||
case "resource_update" as LegalSequenceKind:
|
||||
return <TileOldMarkAs {...props} />;
|
||||
case "set_servo_angle": return <TileSetServoAngle {...props} />;
|
||||
|
@ -161,7 +162,8 @@ export function renderCeleryNode(props: StepParams) {
|
|||
case "reboot": return <TileReboot {...props} />;
|
||||
case "emergency_lock": return <TileEmergencyStop {...props} />;
|
||||
case "assertion": return <TileAssertion {...props} />;
|
||||
case "sync": case "power_off": case "read_status":
|
||||
case "power_off": return <TileShutdown {...props} />;
|
||||
case "sync": case "read_status":
|
||||
case "emergency_unlock": case "install_first_party_farmware":
|
||||
return <TileSystemAction {...props} />;
|
||||
case "check_updates": case "factory_reset":
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
import { Row, Col, FBSelect, DropDownItem } from "../../ui/index";
|
||||
import { StepParams } from "../interfaces";
|
||||
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
|
||||
import { ToolTips } from "../../constants";
|
||||
import * as React from "react";
|
||||
import { unpackStep } from "./mark_as/unpack_step";
|
||||
import { UpdateResource } from "farmbot";
|
||||
import { resourceList } from "./mark_as/resource_list";
|
||||
import { actionList } from "./mark_as/action_list";
|
||||
import { commitStepChanges } from "./mark_as/commit_step_changes";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
interface MarkAsState { nextResource: DropDownItem | undefined }
|
||||
const NONE = (): DropDownItem => ({ label: t("Select one"), value: 0 });
|
||||
|
||||
export class MarkAs extends React.Component<StepParams, MarkAsState> {
|
||||
state: MarkAsState = { nextResource: undefined };
|
||||
className = "update-resource-step";
|
||||
|
||||
commitSelection = (nextAction: DropDownItem) => {
|
||||
this.props.dispatch(commitStepChanges({
|
||||
index: this.props.index,
|
||||
nextAction,
|
||||
nextResource: this.state.nextResource,
|
||||
sequence: this.props.currentSequence,
|
||||
step: this.props.currentStep as UpdateResource,
|
||||
}));
|
||||
this.setState({ nextResource: undefined });
|
||||
};
|
||||
|
||||
render() {
|
||||
const step = this.props.currentStep as UpdateResource;
|
||||
const { rightSide, leftSide } =
|
||||
unpackStep({ step, resourceIndex: this.props.resources });
|
||||
return <StepWrapper>
|
||||
<StepHeader
|
||||
className={this.className}
|
||||
helpText={ToolTips.MARK_AS}
|
||||
currentSequence={this.props.currentSequence}
|
||||
currentStep={this.props.currentStep}
|
||||
dispatch={this.props.dispatch}
|
||||
index={this.props.index}
|
||||
confirmStepDeletion={this.props.confirmStepDeletion} />
|
||||
<StepContent className={this.className}>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<label>{t("Mark")}</label>
|
||||
<FBSelect
|
||||
list={resourceList(this.props.resources)}
|
||||
onChange={(nextResource) => this.setState({ nextResource })}
|
||||
allowEmpty={false}
|
||||
selectedItem={this.state.nextResource || leftSide} />
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<label>{t("as")}</label>
|
||||
<FBSelect
|
||||
list={actionList(this.state.nextResource?.headingId,
|
||||
step, this.props.resources)}
|
||||
onChange={this.commitSelection}
|
||||
key={JSON.stringify(rightSide) + JSON.stringify(this.state)}
|
||||
selectedItem={this.state.nextResource ? NONE() : rightSide} />
|
||||
</Col>
|
||||
</Row>
|
||||
</StepContent>
|
||||
</StepWrapper>;
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
import { actionList } from "../action_list";
|
||||
import { updateResource, markAsResourceFixture } from "../test_support";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../../__test_support__/resource_index_builder";
|
||||
import { PLANT_OPTIONS } from "../constants";
|
||||
|
||||
describe("actionList()", () => {
|
||||
it("uses args.resource_type if DropDownItem is undefined", () => {
|
||||
const step = updateResource({
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 0 }
|
||||
});
|
||||
const { index } = markAsResourceFixture();
|
||||
const result = actionList(undefined, step, index);
|
||||
expect(result).toEqual(PLANT_OPTIONS());
|
||||
});
|
||||
|
||||
it("provides a list of tool mount actions", () => {
|
||||
const ddi = { label: "test case", value: 1, headingId: "Device" };
|
||||
const step = updateResource();
|
||||
const { index } = markAsResourceFixture();
|
||||
const result = actionList(ddi.headingId, step, index);
|
||||
expect(result.length).toBe(3);
|
||||
const labels = result.map(x => x.label);
|
||||
expect(labels).toContain("Not Mounted");
|
||||
expect(labels).toContain("Mounted to: T1");
|
||||
expect(labels).toContain("Mounted to: T2");
|
||||
});
|
||||
|
||||
it("provides a list of generic pointer actions", () => {
|
||||
const ddi = { label: "test case", value: 1, headingId: "GenericPointer" };
|
||||
const step = updateResource();
|
||||
const { index } = markAsResourceFixture();
|
||||
const result = actionList(ddi.headingId, step, index);
|
||||
expect(result.length).toBe(1);
|
||||
const labels = result.map(x => x.label);
|
||||
expect(labels).toContain("Removed");
|
||||
});
|
||||
|
||||
it("provides a list of weed pointer actions", () => {
|
||||
const ddi = { label: "test case", value: 1, headingId: "Weed" };
|
||||
const step = updateResource();
|
||||
const { index } = markAsResourceFixture();
|
||||
const result = actionList(ddi.headingId, step, index);
|
||||
expect(result.length).toBe(1);
|
||||
const labels = result.map(x => x.label);
|
||||
expect(labels).toContain("Removed");
|
||||
});
|
||||
|
||||
it("returns an empty list for identifiers", () => {
|
||||
const ddi = { label: "test case", value: 1, headingId: "USB Cables" };
|
||||
const step = updateResource();
|
||||
const { index } = buildResourceIndex([]);
|
||||
const result = actionList(ddi.headingId, step, index);
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
|
||||
it("returns an empty list for all other options", () => {
|
||||
const step = updateResource({ kind: "identifier", args: { label: "var" } });
|
||||
const { index } = buildResourceIndex([]);
|
||||
const result = actionList("Other", step, index);
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
import { fakeMarkAsProps } from "../test_support";
|
||||
import { commitStepChanges } from "../commit_step_changes";
|
||||
import { UpdateResource, TaggedSequence } from "farmbot";
|
||||
import { Actions } from "../../../../constants";
|
||||
import { unpackUUID } from "../../../../util";
|
||||
|
||||
describe("commitSelection", () => {
|
||||
it("commits changes in a <MarkAs/> component", () => {
|
||||
const p = fakeMarkAsProps();
|
||||
const results = commitStepChanges({
|
||||
nextAction: { label: "X", value: "some_action" },
|
||||
nextResource: undefined,
|
||||
step: p.currentStep as UpdateResource,
|
||||
index: p.index,
|
||||
sequence: p.currentSequence
|
||||
});
|
||||
expect(results.type).toBe(Actions.OVERWRITE_RESOURCE);
|
||||
const { payload } = results;
|
||||
expect(unpackUUID(payload.uuid).kind).toBe("Sequence");
|
||||
const s = payload.update as TaggedSequence["body"];
|
||||
expect(s.kind).toBe("sequence");
|
||||
const step = (s.body || [])[0] as UpdateResource;
|
||||
expect(step.body?.[0].args.value).toBe("some_action");
|
||||
});
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
jest.mock("../commit_step_changes", () => {
|
||||
return {
|
||||
commitStepChanges: jest.fn()
|
||||
};
|
||||
});
|
||||
import * as React from "react";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { MarkAs } from "../../mark_as";
|
||||
import { FBSelect } from "../../../../ui";
|
||||
import { fakeMarkAsProps } from "../test_support";
|
||||
import { commitStepChanges } from "../commit_step_changes";
|
||||
|
||||
describe("<MarkAs/>", () => {
|
||||
it("renders the basic parts", () => {
|
||||
const el = mount(<MarkAs {...fakeMarkAsProps()} />);
|
||||
const text = el.text();
|
||||
expect(text).toContain("Tool Mount");
|
||||
expect(text).toContain("Not Mounted");
|
||||
});
|
||||
|
||||
it("selects a resource", () => {
|
||||
const el = shallow(<MarkAs {...fakeMarkAsProps()} />);
|
||||
const wow = el.find(FBSelect).first();
|
||||
expect(wow).toBeTruthy();
|
||||
const nextResource = {
|
||||
label: "fake resource",
|
||||
value: "fake_resource"
|
||||
};
|
||||
wow.simulate("change", nextResource);
|
||||
expect(el.state()).toEqual({ nextResource });
|
||||
});
|
||||
|
||||
it("triggers callbacks (commitSelection)", () => {
|
||||
const props = fakeMarkAsProps();
|
||||
const i = new MarkAs(props);
|
||||
i.setState = jest.fn((s: typeof i.state) => {
|
||||
i.state = s;
|
||||
});
|
||||
const nextResource = { label: "should be cleared", value: 1 };
|
||||
i.setState({ nextResource });
|
||||
expect(i.state.nextResource).toEqual(nextResource);
|
||||
i.commitSelection({ label: "stub", value: "mock" });
|
||||
expect(i.state.nextResource).toBe(undefined);
|
||||
expect(commitStepChanges).toHaveBeenCalled();
|
||||
expect(i.state.nextResource).toEqual(undefined);
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
import { updateResource } from "../test_support";
|
||||
import { packStep } from "../pack_step";
|
||||
import { TOP_HALF } from "../constants";
|
||||
import { Resource, Identifier } from "farmbot";
|
||||
|
||||
describe("packStep()", () => {
|
||||
const plant = updateResource({
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 6 }
|
||||
});
|
||||
|
||||
it("serializes 'plant_stage' actions", () => {
|
||||
const actionDDI = { value: "harvested", label: "harvested" };
|
||||
const { args, body } = packStep(plant, undefined, actionDDI);
|
||||
expect(body?.[0].args.label).toEqual("plant_stage");
|
||||
expect(body?.[0].args.value).toEqual("harvested");
|
||||
expect((args.resource as Resource).args.resource_id).toEqual(6);
|
||||
expect((args.resource as Resource).args.resource_type).toEqual("Plant");
|
||||
});
|
||||
|
||||
it("serializes 'mounted_tool_id' actions", () => {
|
||||
const resourceDDI = TOP_HALF[0];
|
||||
const actionDDI = { value: 23, label: "Mounted to can opener" };
|
||||
const device = updateResource({
|
||||
kind: "resource",
|
||||
args: { resource_type: "Device", resource_id: 7 }
|
||||
});
|
||||
const { args, body } = packStep(device, resourceDDI, actionDDI);
|
||||
expect(body?.[0].args.label).toEqual("mounted_tool_id");
|
||||
expect((args.resource as Resource).args.resource_type).toEqual("Device");
|
||||
expect((args.resource as Resource).args.resource_id).toEqual(0);
|
||||
expect(body?.[0].args.value).toEqual(23);
|
||||
});
|
||||
|
||||
it("serializes 'plant_stage' actions: identifier", () => {
|
||||
const actionDDI = { value: "harvested", label: "harvested" };
|
||||
const identifier = updateResource({
|
||||
kind: "identifier", args: { label: "var" }
|
||||
});
|
||||
const { args, body } = packStep(identifier, undefined, actionDDI);
|
||||
expect(body?.[0].args.label).toEqual("plant_stage");
|
||||
expect(body?.[0].args.value).toEqual("harvested");
|
||||
expect((args.resource as Identifier).args.label).toEqual("var");
|
||||
});
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
import { resourceList } from "../resource_list";
|
||||
import { markAsResourceFixture } from "../test_support";
|
||||
|
||||
describe("resourceList()", () => {
|
||||
it("lists defaults, plus saved points", () => {
|
||||
const { index } = markAsResourceFixture();
|
||||
const result = resourceList(index);
|
||||
expect(result.length).toBeTruthy();
|
||||
const headings = result.filter(x => x.heading).map(x => x.label);
|
||||
expect(headings).toContain("Device");
|
||||
expect(headings).toContain("Plants");
|
||||
expect(headings).toContain("Points");
|
||||
expect(headings).toContain("Weeds");
|
||||
const weeds = result.filter(x => x.headingId == "Weed");
|
||||
expect(weeds.length).toEqual(2);
|
||||
expect(weeds[1].label).toEqual("weed 1 (200, 400, 0)");
|
||||
});
|
||||
});
|
|
@ -1,144 +0,0 @@
|
|||
import { fakeResourceIndex } from "../../../locals_list/test_helpers";
|
||||
import { updateResource } from "../test_support";
|
||||
import { unpackStep, TOOL_MOUNT, DISMOUNTED } from "../unpack_step";
|
||||
import {
|
||||
selectAllPlantPointers,
|
||||
selectAllTools,
|
||||
selectAllWeedPointers,
|
||||
} from "../../../../resources/selectors";
|
||||
import { DropDownPair } from "../interfaces";
|
||||
import { fakeTool, fakeWeed } from "../../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../../__test_support__/resource_index_builder";
|
||||
describe("unpackStep()", () => {
|
||||
function assertGoodness(result: DropDownPair,
|
||||
action_label: string,
|
||||
action_value: string,
|
||||
resource_label: string,
|
||||
resource_value: string | number): void {
|
||||
expect(result.rightSide.label).toBe(action_label);
|
||||
expect(result.rightSide.value).toBe(action_value);
|
||||
expect(result.leftSide.label).toBe(resource_label);
|
||||
expect(result.leftSide.value).toBe(resource_value);
|
||||
}
|
||||
|
||||
it("unpacks empty tool_ids", () => {
|
||||
const result = unpackStep({
|
||||
step: updateResource(undefined, { label: "mounted_tool_id", value: 0 }),
|
||||
resourceIndex: fakeResourceIndex()
|
||||
});
|
||||
expect(result).toEqual(DISMOUNTED());
|
||||
});
|
||||
|
||||
it("unpacks valid tool_ids", () => {
|
||||
const resourceIndex = fakeResourceIndex();
|
||||
const { body } = selectAllTools(resourceIndex)[0];
|
||||
expect(body).toBeTruthy();
|
||||
|
||||
const result = unpackStep({
|
||||
step: updateResource(undefined,
|
||||
{ label: "mounted_tool_id", value: body.id || NaN }),
|
||||
resourceIndex
|
||||
});
|
||||
const actionLabel = "Mounted to: Generic Tool";
|
||||
const { label, value } = TOOL_MOUNT();
|
||||
assertGoodness(result, actionLabel, "mounted", label, value);
|
||||
});
|
||||
|
||||
it("unpacks valid tool_ids with missing names", () => {
|
||||
const tool = fakeTool();
|
||||
tool.body.id = 1;
|
||||
tool.body.name = undefined;
|
||||
const resourceIndex = buildResourceIndex([tool]).index;
|
||||
const { body } = selectAllTools(resourceIndex)[0];
|
||||
expect(body).toBeTruthy();
|
||||
|
||||
const result = unpackStep({
|
||||
step: updateResource(undefined,
|
||||
{ label: "mounted_tool_id", value: body.id || NaN }),
|
||||
resourceIndex
|
||||
});
|
||||
const actionLabel = "Mounted to: Untitled Tool";
|
||||
const { label, value } = TOOL_MOUNT();
|
||||
assertGoodness(result, actionLabel, "mounted", label, value);
|
||||
});
|
||||
|
||||
it("unpacks invalid tool_ids (that may have been valid previously)", () => {
|
||||
const result = unpackStep({
|
||||
step: updateResource(undefined,
|
||||
{ label: "mounted_tool_id", value: Infinity }),
|
||||
resourceIndex: fakeResourceIndex()
|
||||
});
|
||||
const actionLabel = "Mounted to: an unknown tool";
|
||||
const { label, value } = TOOL_MOUNT();
|
||||
assertGoodness(result, actionLabel, "mounted", label, value);
|
||||
});
|
||||
|
||||
it("unpacks plant_stage operations: plants", () => {
|
||||
const resourceIndex = fakeResourceIndex();
|
||||
const plant = selectAllPlantPointers(resourceIndex)[1];
|
||||
expect(plant).toBeTruthy();
|
||||
|
||||
const result = unpackStep({
|
||||
step: updateResource({
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: plant.body.id || -1 }
|
||||
},
|
||||
{ label: "plant_stage", value: "wilting" }),
|
||||
resourceIndex
|
||||
});
|
||||
const { body } = plant;
|
||||
const plantName = `${body.name} (${body.x}, ${body.y}, ${body.z})`;
|
||||
assertGoodness(result, "wilting", "wilting", plantName, body.id || NaN);
|
||||
});
|
||||
|
||||
it("unpacks plant_stage operations: weeds", () => {
|
||||
const resourceIndex = fakeResourceIndex([fakeWeed()]);
|
||||
const weed = selectAllWeedPointers(resourceIndex)[1];
|
||||
expect(weed).toBeTruthy();
|
||||
|
||||
const result = unpackStep({
|
||||
step: updateResource({
|
||||
kind: "resource",
|
||||
args: { resource_type: "Weed", resource_id: weed.body.id || -1 }
|
||||
},
|
||||
{ label: "plant_stage", value: "removed" }),
|
||||
resourceIndex
|
||||
});
|
||||
const { body } = weed;
|
||||
const plantName = `${body.name} (${body.x}, ${body.y}, ${body.z})`;
|
||||
assertGoodness(result, "Removed", "removed", plantName, body.id || NaN);
|
||||
});
|
||||
|
||||
it("unpacks plant_stage operations: identifier", () => {
|
||||
const resourceIndex = fakeResourceIndex();
|
||||
const result = unpackStep({
|
||||
step: updateResource(
|
||||
{ kind: "identifier", args: { label: "var" } },
|
||||
{ label: "plant_stage", value: "removed" }),
|
||||
resourceIndex
|
||||
});
|
||||
assertGoodness(result, "Removed", "removed", "var", "var");
|
||||
});
|
||||
|
||||
it("unpacks unknown resource update_resource steps", () => {
|
||||
const result = unpackStep({
|
||||
step: updateResource(),
|
||||
resourceIndex: fakeResourceIndex()
|
||||
});
|
||||
assertGoodness(result,
|
||||
"some_value", "some_value",
|
||||
"Other 1 some_attr", "some_attr");
|
||||
});
|
||||
|
||||
it("unpacks unknown identifier update_resource steps", () => {
|
||||
const result = unpackStep({
|
||||
step: updateResource({ kind: "identifier", args: { label: "var" } }),
|
||||
resourceIndex: fakeResourceIndex()
|
||||
});
|
||||
assertGoodness(result,
|
||||
"some_value", "some_value",
|
||||
"variable 0 some_attr", "some_attr");
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
import { Dictionary } from "farmbot";
|
||||
import { DropDownItem } from "../../../ui";
|
||||
import { ListBuilder } from "./interfaces";
|
||||
import { ResourceIndex } from "../../../resources/interfaces";
|
||||
import { UpdateResource } from "farmbot";
|
||||
import { selectAllTools } from "../../../resources/selectors";
|
||||
import {
|
||||
MOUNTED_TO,
|
||||
DISMOUNT,
|
||||
PLANT_OPTIONS,
|
||||
POINT_OPTIONS,
|
||||
} from "./constants";
|
||||
|
||||
const allToolsAsDDI = (i: ResourceIndex) => {
|
||||
return selectAllTools(i)
|
||||
.filter(x => !!x.body.id)
|
||||
.map(x => {
|
||||
return {
|
||||
label: `${MOUNTED_TO()} ${x.body.name}`,
|
||||
value: x.body.id || 0
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const DEFAULT = "Default";
|
||||
|
||||
const ACTION_LIST: Dictionary<ListBuilder> = {
|
||||
"Device": (i) => [DISMOUNT(), ...allToolsAsDDI(i)],
|
||||
"Plant": () => PLANT_OPTIONS(),
|
||||
"GenericPointer": () => POINT_OPTIONS(),
|
||||
"Weed": () => POINT_OPTIONS(),
|
||||
[DEFAULT]: () => []
|
||||
};
|
||||
|
||||
const getList = (t: string): ListBuilder =>
|
||||
(ACTION_LIST[t] || ACTION_LIST[DEFAULT]);
|
||||
|
||||
export const actionList = (d: string | undefined,
|
||||
r: UpdateResource,
|
||||
i: ResourceIndex): DropDownItem[] => {
|
||||
const resourceType = r.args.resource.kind == "identifier"
|
||||
? DEFAULT
|
||||
: r.args.resource.args.resource_type;
|
||||
return getList(d || resourceType)(i);
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
import { UpdateResource } from "farmbot";
|
||||
import { editStep } from "../../../api/crud";
|
||||
import { packStep } from "./pack_step";
|
||||
import { MarkAsEditProps } from "./interfaces";
|
||||
|
||||
/** A wrapper for the `editStep()` action creator.
|
||||
* Isolated from UI for ease of testing. */
|
||||
export const commitStepChanges = (p: MarkAsEditProps) => {
|
||||
const { step, nextResource, nextAction, index, sequence } = p;
|
||||
return editStep({
|
||||
step,
|
||||
index,
|
||||
sequence,
|
||||
executor(c: UpdateResource) {
|
||||
const { args, body } = packStep(step, nextResource, nextAction);
|
||||
c.args = args;
|
||||
c.body = body;
|
||||
}
|
||||
});
|
||||
};
|
|
@ -1,45 +0,0 @@
|
|||
import { DropDownItem } from "../../../ui";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { PLANT_STAGE_LIST } from "../../../farm_designer/plants/edit_plant_status";
|
||||
|
||||
export const MOUNTED_TO = () => t("Mounted to:");
|
||||
|
||||
export const DISMOUNT = (): DropDownItem =>
|
||||
({ label: t("Not Mounted"), value: 0 });
|
||||
|
||||
/** Legal "actions" for "Mark As.." block when marking Point resources */
|
||||
export const POINT_OPTIONS = (): DropDownItem[] => [
|
||||
{ label: t("Removed"), value: "removed" },
|
||||
];
|
||||
|
||||
/** Legal "actions" in the "Mark As.." block when operating on
|
||||
* a Plant resource. */
|
||||
export const PLANT_OPTIONS = PLANT_STAGE_LIST;
|
||||
|
||||
const value = 0; // Not used in headings.
|
||||
|
||||
export const PLANT_HEADER: DropDownItem = {
|
||||
headingId: "Plant",
|
||||
label: t("Plants"),
|
||||
value,
|
||||
heading: true
|
||||
};
|
||||
|
||||
export const POINT_HEADER: DropDownItem = {
|
||||
headingId: "GenericPointer",
|
||||
label: t("Points"),
|
||||
value,
|
||||
heading: true
|
||||
};
|
||||
|
||||
export const WEED_HEADER: DropDownItem = {
|
||||
headingId: "Weed",
|
||||
label: t("Weeds"),
|
||||
value,
|
||||
heading: true
|
||||
};
|
||||
|
||||
export const TOP_HALF = [
|
||||
{ headingId: "Device", label: t("Device"), value, heading: true },
|
||||
{ headingId: "Device", label: t("Tool Mount"), value },
|
||||
];
|
|
@ -1,37 +0,0 @@
|
|||
import { ResourceIndex } from "../../../resources/interfaces";
|
||||
import { DropDownItem } from "../../../ui";
|
||||
import { UpdateResource, TaggedSequence, Resource, Identifier } from "farmbot";
|
||||
|
||||
/** Function that converts resources into dropdown selections based on
|
||||
* use-case specific rules */
|
||||
export type ListBuilder = (i: ResourceIndex) => DropDownItem[];
|
||||
|
||||
/** Input data for calls to commitStepChanges() */
|
||||
export interface MarkAsEditProps {
|
||||
nextAction: DropDownItem;
|
||||
nextResource: DropDownItem | undefined;
|
||||
step: UpdateResource;
|
||||
index: number;
|
||||
sequence: TaggedSequence
|
||||
}
|
||||
|
||||
export interface PackedStepWithResourceIndex {
|
||||
step: UpdateResource;
|
||||
resourceIndex: ResourceIndex;
|
||||
}
|
||||
|
||||
export interface UnpackedStepWithResourceIndex {
|
||||
resource: Resource | Identifier;
|
||||
field: string;
|
||||
value: string | number | boolean;
|
||||
resourceIndex: ResourceIndex;
|
||||
}
|
||||
|
||||
/** A pair of DropDownItems used to render the currently selected items in the
|
||||
* "Mark As.." block. */
|
||||
export interface DropDownPair {
|
||||
/** Left side drop down */
|
||||
leftSide: DropDownItem;
|
||||
/** Right side drop down */
|
||||
rightSide: DropDownItem;
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
import { UpdateResource, Resource, Identifier, resource_type } from "farmbot";
|
||||
import { DropDownItem } from "../../../ui";
|
||||
|
||||
/**
|
||||
* This is a support function for the <MarkAs/> component.
|
||||
*
|
||||
* SCENARIO: You are editing a `Mark As..` sequence step. The user has unsaved
|
||||
* local changes as well as a copy of older data from the API.
|
||||
*
|
||||
* PROBLEM: You need to take the component's local state plus the
|
||||
* shape of the "update_resource" ("Mark As..") block and merge them
|
||||
* together so that you can render the form in the editor.
|
||||
*
|
||||
* SOLUTION: Use the celery node + pieces of the component's state (resourceDDI,
|
||||
* actionDDI) to properly populate dropdown menus and determine the
|
||||
* shape of the new "update_resource" step when it is saved.
|
||||
* */
|
||||
export const packStep = (
|
||||
csNode: UpdateResource,
|
||||
resourceDDI: DropDownItem | undefined,
|
||||
actionDDI: DropDownItem,
|
||||
): UpdateResource => {
|
||||
const resource = resourceDDI?.headingId
|
||||
? resourceNode(resourceDDI.headingId, resourceDDI.value)
|
||||
: csNode.args.resource;
|
||||
if (resource.kind == "identifier") {
|
||||
return updateResource(resource, "plant_stage", actionDDI.value);
|
||||
} else {
|
||||
switch (resource.args.resource_type) {
|
||||
case "Device":
|
||||
/* Scenario I: Changing tool mount */
|
||||
return updateResource(resource, "mounted_tool_id", actionDDI.value);
|
||||
default:
|
||||
/* Scenario II: Changing a point */
|
||||
return updateResource(resource, "plant_stage", actionDDI.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resourceNode = (type: string, id: string | number): Resource => ({
|
||||
kind: "resource",
|
||||
args: {
|
||||
resource_type: type as resource_type,
|
||||
resource_id: parseInt("" + id),
|
||||
}
|
||||
});
|
||||
|
||||
const updateResource = (
|
||||
resource: Resource | Identifier,
|
||||
field: string,
|
||||
value: string | number,
|
||||
): UpdateResource => ({
|
||||
kind: "update_resource",
|
||||
args: { resource },
|
||||
body: [{
|
||||
kind: "pair", args: {
|
||||
label: field,
|
||||
value: value,
|
||||
}
|
||||
}]
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
import { ResourceIndex } from "../../../resources/interfaces";
|
||||
import { DropDownItem } from "../../../ui/fb_select";
|
||||
import { selectAllPoints } from "../../../resources/selectors";
|
||||
import { TaggedPoint } from "farmbot";
|
||||
import { Point } from "farmbot/dist/resources/api_resources";
|
||||
import { POINT_HEADER, PLANT_HEADER, TOP_HALF, WEED_HEADER } from "./constants";
|
||||
|
||||
/** Filter function to remove resources we don't care about,
|
||||
* such as ToolSlots and unsaved (Plant|Point)'s */
|
||||
const isRelevant = (x: TaggedPoint) => {
|
||||
const saved = !!x.body.id;
|
||||
const notToolSlot = x.body.pointer_type !== "ToolSlot";
|
||||
return saved && notToolSlot;
|
||||
};
|
||||
|
||||
/** Format DropDownItem["label"] as "Name (1, 2, 3)" */
|
||||
const labelStr =
|
||||
(n: string, x: number, y: number, z: number) => `${n} (${x}, ${y}, ${z})`;
|
||||
|
||||
/** Convert a Point to a DropDownItem that is formatted appropriately
|
||||
* for the "Mark As.." step. */
|
||||
export const point2ddi = (i: Point): DropDownItem => {
|
||||
const { x, y, z, name, id, pointer_type } = i;
|
||||
return {
|
||||
value: id || 0,
|
||||
label: labelStr(name, x, y, z),
|
||||
headingId: pointer_type,
|
||||
};
|
||||
};
|
||||
|
||||
/** GIVEN: mixed list of *SAVED* point types (ToolSlot, Plant, Pointer)
|
||||
* RETURNS: list of DropDownItems with proper headers and `headerId`s */
|
||||
const pointList =
|
||||
(input: TaggedPoint[]): DropDownItem[] => {
|
||||
const genericPoints: DropDownItem[] = [POINT_HEADER];
|
||||
const weeds: DropDownItem[] = [WEED_HEADER];
|
||||
const plants: DropDownItem[] = [PLANT_HEADER];
|
||||
input
|
||||
.map(x => x.body)
|
||||
.forEach(body => {
|
||||
switch (body.pointer_type) {
|
||||
case "GenericPointer": return genericPoints.push(point2ddi(body));
|
||||
case "Weed": return weeds.push(point2ddi(body));
|
||||
case "Plant": return plants.push(point2ddi(body));
|
||||
}
|
||||
});
|
||||
return [...plants, ...genericPoints, ...weeds];
|
||||
};
|
||||
|
||||
/** Creates a formatted DropDownItem list for the "Resource" (left hand) side of
|
||||
* the "Mark As" step. */
|
||||
export const resourceList = (r: ResourceIndex): DropDownItem[] => {
|
||||
return [...TOP_HALF, ...pointList(selectAllPoints(r).filter(isRelevant))];
|
||||
};
|
|
@ -1,73 +0,0 @@
|
|||
import {
|
||||
UpdateResource, TaggedSequence, resource_type, Pair, Resource, Identifier,
|
||||
} from "farmbot";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import {
|
||||
fakeTool,
|
||||
fakePlant,
|
||||
fakePoint,
|
||||
fakeSequence,
|
||||
fakeWeed,
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { betterMerge } from "../../../util";
|
||||
import { MarkAs } from "../mark_as";
|
||||
|
||||
export function updateResource(
|
||||
resource?: Resource | Identifier, pairArgs?: Pair["args"]): UpdateResource {
|
||||
return {
|
||||
kind: "update_resource",
|
||||
args: {
|
||||
resource: resource || {
|
||||
kind: "resource", args: {
|
||||
resource_type: "Other" as resource_type,
|
||||
resource_id: 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
body: [{
|
||||
kind: "pair", args: {
|
||||
label: "some_attr",
|
||||
value: "some_value",
|
||||
...pairArgs,
|
||||
}
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
export const markAsResourceFixture = () => buildResourceIndex([
|
||||
betterMerge(fakeTool(), { body: { name: "T1", id: 1 } }),
|
||||
fakePlant(),
|
||||
betterMerge(fakeTool(), { body: { name: "T2", id: 2 } }),
|
||||
betterMerge(fakePoint(), { body: { name: "my point", id: 7 } }),
|
||||
betterMerge(fakeWeed(), { body: { name: "weed 1", id: 8 } }),
|
||||
betterMerge(fakeTool(), { body: { name: "T3", id: undefined } }),
|
||||
]);
|
||||
|
||||
export function fakeMarkAsProps() {
|
||||
const steps: TaggedSequence["body"]["body"] = [
|
||||
{
|
||||
kind: "update_resource",
|
||||
args: {
|
||||
resource: {
|
||||
kind: "resource",
|
||||
args: { resource_id: 0, resource_type: "Device" }
|
||||
}
|
||||
},
|
||||
body: [{ kind: "pair", args: { label: "mounted_tool_id", value: 0 } }],
|
||||
},
|
||||
];
|
||||
const currentSequence: TaggedSequence =
|
||||
betterMerge(fakeSequence(), { body: { body: steps } });
|
||||
const props: MarkAs["props"] = {
|
||||
currentSequence,
|
||||
dispatch: jest.fn(),
|
||||
index: 0,
|
||||
currentStep: steps[0],
|
||||
resources: buildResourceIndex([currentSequence]).index,
|
||||
confirmStepDeletion: false
|
||||
};
|
||||
|
||||
return props;
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
import { DropDownItem } from "../../../ui";
|
||||
import {
|
||||
findToolById,
|
||||
findPointerByTypeAndId,
|
||||
} from "../../../resources/selectors";
|
||||
import { point2ddi } from "./resource_list";
|
||||
import { MOUNTED_TO } from "./constants";
|
||||
import {
|
||||
DropDownPair, PackedStepWithResourceIndex, UnpackedStepWithResourceIndex,
|
||||
} from "./interfaces";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import {
|
||||
PLANT_STAGE_DDI_LOOKUP,
|
||||
} from "../../../farm_designer/plants/edit_plant_status";
|
||||
|
||||
export const TOOL_MOUNT = (): DropDownItem => ({
|
||||
label: t("Tool Mount"), value: "tool_mount"
|
||||
});
|
||||
const NOT_IN_USE = (): DropDownItem => ({ label: t("Not Mounted"), value: 0 });
|
||||
export const DISMOUNTED = (): DropDownPair => ({
|
||||
leftSide: TOOL_MOUNT(),
|
||||
rightSide: NOT_IN_USE()
|
||||
});
|
||||
const DEFAULT_TOOL_NAME = () => t("Untitled Tool");
|
||||
|
||||
const mountedTo = (toolName = DEFAULT_TOOL_NAME()): DropDownItem =>
|
||||
({ label: `${MOUNTED_TO()} ${toolName}`, value: "mounted" });
|
||||
|
||||
/** The user wants to change the `mounted_tool_id` of their Device. */
|
||||
function mountTool(i: UnpackedStepWithResourceIndex): DropDownPair {
|
||||
const { value } = i;
|
||||
if (typeof value === "number" && value > 0) {
|
||||
try { // Good tool id
|
||||
const tool = findToolById(i.resourceIndex, value as number);
|
||||
return { leftSide: TOOL_MOUNT(), rightSide: mountedTo(tool.body.name) };
|
||||
} catch { // Bad tool ID or app still loading.
|
||||
return { leftSide: TOOL_MOUNT(), rightSide: mountedTo("an unknown tool") };
|
||||
}
|
||||
} else {
|
||||
// No tool id
|
||||
return DISMOUNTED();
|
||||
}
|
||||
}
|
||||
|
||||
/** When we can't properly guess the correct way to to render the screen,
|
||||
* possibly for legacy reasons or because the user wrote their CeleryScript by
|
||||
* hand. */
|
||||
function unknownOption(i: UnpackedStepWithResourceIndex): DropDownPair {
|
||||
const { resource } = i;
|
||||
const resource_type =
|
||||
resource.kind == "resource" ? resource.args.resource_type : "variable";
|
||||
const resource_id =
|
||||
resource.kind == "resource" ? resource.args.resource_id : 0;
|
||||
const { field, value } = i;
|
||||
const leftLabel = `${resource_type} ${resource_id} ${field}`;
|
||||
return {
|
||||
leftSide: { label: leftLabel, value: field },
|
||||
rightSide: { label: "" + value, value: "" + value }
|
||||
};
|
||||
}
|
||||
|
||||
/** The user wants to mark a the `plant_stage` attribute of a Plant resource. */
|
||||
function plantStage(i: UnpackedStepWithResourceIndex): DropDownPair {
|
||||
const { resource } = i;
|
||||
const resource_type =
|
||||
resource.kind == "resource" ? resource.args.resource_type : "";
|
||||
const resource_id =
|
||||
resource.kind == "resource" ? resource.args.resource_id : 0;
|
||||
const { value } = i;
|
||||
const leftSide = resource.kind == "resource"
|
||||
? point2ddi(findPointerByTypeAndId(
|
||||
i.resourceIndex, resource_type, resource_id).body)
|
||||
: { label: "" + resource.args.label, value: "" + resource.args.label };
|
||||
return {
|
||||
leftSide,
|
||||
rightSide: PLANT_STAGE_DDI_LOOKUP()["" + value]
|
||||
|| { label: "" + value, value: "" + value },
|
||||
};
|
||||
}
|
||||
|
||||
/** We can guess how the "Mark As.." UI will be rendered (left and right side
|
||||
* drop downs) based on the shape of the current step. There are several
|
||||
* strategies and this function will dispatch the appropriate one. */
|
||||
export function unpackStep(p: PackedStepWithResourceIndex): DropDownPair {
|
||||
const { resource } = p.step.args;
|
||||
const { label, value } = p.step.body?.[0]?.args || { label: "", value: "" };
|
||||
const field = label;
|
||||
const unpacked = { resourceIndex: p.resourceIndex, resource, field, value };
|
||||
switch (field) {
|
||||
case "mounted_tool_id": return mountTool(unpacked);
|
||||
case "plant_stage": return plantStage(unpacked);
|
||||
default: return unknownOption(unpacked);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import { TypePart } from "./tile_assertion/type_part";
|
|||
import { LuaPart } from "./tile_assertion/lua_part";
|
||||
import { SequencePart } from "./tile_assertion/sequence_part";
|
||||
import { Assertion } from "farmbot/dist/corpus";
|
||||
import { ToolTips } from "../../constants";
|
||||
|
||||
export interface AssertionStepProps extends StepParams {
|
||||
currentStep: Assertion;
|
||||
|
@ -24,7 +25,7 @@ export function TileAssertion(props: StepParams) {
|
|||
return <StepWrapper>
|
||||
<StepHeader
|
||||
className={CLASS_NAME}
|
||||
helpText={""}
|
||||
helpText={ToolTips.ASSERTION}
|
||||
currentSequence={p.currentSequence}
|
||||
currentStep={p.currentStep}
|
||||
dispatch={p.dispatch}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { StepParams } from "../interfaces";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { ToolTips, Content } from "../../constants";
|
||||
import { StepWrapper, StepHeader, StepContent } from "../step_ui";
|
||||
import { Col, Row } from "../../ui/index";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
@ -21,7 +21,7 @@ export function TileEmergencyStop(props: StepParams) {
|
|||
<Row>
|
||||
<Col xs={12}>
|
||||
<p>
|
||||
{t("Unlocking a device requires user intervention.")}
|
||||
{t(Content.ESTOP_STEP)}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -14,6 +14,6 @@ export function TileIf(props: StepParams) {
|
|||
confirmStepDeletion={props.confirmStepDeletion}
|
||||
showPins={props.showPins} />;
|
||||
} else {
|
||||
return <p> Expected "_if" node</p>;
|
||||
return <p>{"Expected `_if` node"}</p>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,8 +76,23 @@ describe("LHSOptions()", () => {
|
|||
describe("<InnerIf />", () => {
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<InnerIf {...fakeProps()} />);
|
||||
["Variable", "Operator", "Value", "Then Execute", "Else Execute"].map(string =>
|
||||
expect(wrapper.text()).toContain(string));
|
||||
const inputs = wrapper.find("input");
|
||||
const labels = wrapper.find("label");
|
||||
const buttons = wrapper.find("button");
|
||||
expect(inputs.length).toEqual(2);
|
||||
expect(labels.length).toEqual(5);
|
||||
expect(buttons.length).toEqual(4);
|
||||
expect(inputs.first().props().placeholder).toEqual("If ...");
|
||||
expect(labels.at(0).text()).toEqual("Variable");
|
||||
expect(buttons.at(0).text()).toEqual("Pin 0");
|
||||
expect(labels.at(1).text()).toEqual("Operator");
|
||||
expect(buttons.at(1).text()).toEqual("is");
|
||||
expect(labels.at(2).text()).toEqual("Value");
|
||||
expect(inputs.at(1).props().value).toEqual(0);
|
||||
expect(labels.at(3).text()).toEqual("Then Execute");
|
||||
expect(buttons.at(2).text()).toEqual("None");
|
||||
expect(labels.at(4).text()).toEqual("Else Execute");
|
||||
expect(buttons.at(3).text()).toEqual("None");
|
||||
});
|
||||
|
||||
it("is recursive", () => {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import * as React from "react";
|
||||
import { StepParams } from "../interfaces";
|
||||
import { MarkAs } from "./tile_mark_as/component";
|
||||
|
||||
export function TileMarkAs(props: StepParams) {
|
||||
if (props.currentStep.kind === "update_resource") {
|
||||
return <MarkAs
|
||||
currentSequence={props.currentSequence}
|
||||
currentStep={props.currentStep}
|
||||
dispatch={props.dispatch}
|
||||
index={props.index}
|
||||
resources={props.resources}
|
||||
confirmStepDeletion={props.confirmStepDeletion} />;
|
||||
} else {
|
||||
return <p>{"Expected `update_resource` node"}</p>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
const mockEditStep = jest.fn();
|
||||
jest.mock("../../../../api/crud", () => ({ editStep: mockEditStep }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { MarkAs } from "../component";
|
||||
import { MarkAsProps, UpdateResourceValue } from "../interfaces";
|
||||
import { UpdateResource, Identifier, Resource, resource_type } from "farmbot";
|
||||
import {
|
||||
fakeSequence, fakePlant, fakeWeed,
|
||||
} from "../../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../../__test_support__/resource_index_builder";
|
||||
import { editStep } from "../../../../api/crud";
|
||||
import { NOTHING_SELECTED } from "../../../locals_list/handle_select";
|
||||
|
||||
describe("<MarkAs/>", () => {
|
||||
const plant = fakePlant();
|
||||
plant.body.id = 1;
|
||||
const weed = fakeWeed();
|
||||
weed.body.id = 2;
|
||||
|
||||
const fakeProps = (): MarkAsProps => ({
|
||||
currentSequence: fakeSequence(),
|
||||
dispatch: jest.fn(),
|
||||
index: 0,
|
||||
currentStep: ResourceUpdateResourceStep("Device", 1, "mounted_tool_id", 0),
|
||||
resources: buildResourceIndex([plant, weed]).index,
|
||||
confirmStepDeletion: false
|
||||
});
|
||||
|
||||
it("renders the basic parts", () => {
|
||||
const wrapper = mount(<MarkAs {...fakeProps()} />);
|
||||
["Mark", "Tool Mount", "field", "Mounted Tool", "as", "None"].map(string =>
|
||||
expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
||||
it("resets step", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<MarkAs>(<MarkAs {...p} />);
|
||||
wrapper.instance().resetStep();
|
||||
expect(editStep).toHaveBeenCalled();
|
||||
mockEditStep.mock.calls[0][0].executor(p.currentStep);
|
||||
expect(p.currentStep).toEqual({
|
||||
kind: "update_resource",
|
||||
args: { resource: NOTHING_SELECTED },
|
||||
body: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("edits step", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<MarkAs>(<MarkAs {...p} />);
|
||||
wrapper.setState({
|
||||
resource: {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 1 }
|
||||
},
|
||||
fieldsAndValues: [{ field: "plant_stage", value: "planted" }],
|
||||
});
|
||||
wrapper.instance().commitSelection();
|
||||
expect(editStep).toHaveBeenCalled();
|
||||
mockEditStep.mock.calls[0][0].executor(p.currentStep);
|
||||
expect(p.currentStep).toEqual(
|
||||
ResourceUpdateResourceStep("Plant", 1, "plant_stage", "planted"));
|
||||
});
|
||||
|
||||
it("doesn't edit step", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<MarkAs>(<MarkAs {...p} />);
|
||||
wrapper.setState({
|
||||
resource: { kind: "nothing", args: {} },
|
||||
fieldsAndValues: [{ field: "plant_stage", value: "planted" }],
|
||||
});
|
||||
wrapper.instance().commitSelection();
|
||||
expect(editStep).toHaveBeenCalled();
|
||||
mockEditStep.mock.calls[0][0].executor(p.currentStep);
|
||||
expect(p.currentStep).toEqual(
|
||||
ResourceUpdateResourceStep("Device", 1, "mounted_tool_id", 0));
|
||||
});
|
||||
|
||||
it("doesn't save partial pairs", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<MarkAs>(<MarkAs {...p} />);
|
||||
wrapper.setState({
|
||||
resource: {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 1 }
|
||||
},
|
||||
fieldsAndValues: [
|
||||
{ field: "plant_stage", value: "planted" },
|
||||
{ field: "x", value: 1 },
|
||||
{ field: "y", value: undefined },
|
||||
],
|
||||
});
|
||||
wrapper.instance().commitSelection();
|
||||
expect(editStep).toHaveBeenCalled();
|
||||
mockEditStep.mock.calls[0][0].executor(p.currentStep);
|
||||
const expectedStep =
|
||||
ResourceUpdateResourceStep("Plant", 1, "plant_stage", "planted");
|
||||
expectedStep.body && expectedStep.body.push({
|
||||
kind: "pair", args: { label: "x", value: 1 }
|
||||
});
|
||||
expect(p.currentStep).toEqual(expectedStep);
|
||||
});
|
||||
|
||||
it("edits step to use identifier", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<MarkAs>(<MarkAs {...p} />);
|
||||
wrapper.setState({
|
||||
resource: { kind: "identifier", args: { label: "var" } },
|
||||
fieldsAndValues: [{ field: "plant_stage", value: "planted" }],
|
||||
});
|
||||
wrapper.instance().commitSelection();
|
||||
expect(editStep).toHaveBeenCalled();
|
||||
mockEditStep.mock.calls[0][0].executor(p.currentStep);
|
||||
expect(p.currentStep).toEqual(
|
||||
IdentifierUpdateResourceStep("var", "plant_stage", "planted"));
|
||||
});
|
||||
|
||||
it("updates resource", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<MarkAs>(<MarkAs {...p} />);
|
||||
expect(wrapper.state().resource).toEqual(p.currentStep.args.resource);
|
||||
expect(wrapper.state().fieldsAndValues)
|
||||
.toEqual([{ field: "mounted_tool_id", value: 0 }]);
|
||||
const newResource: Resource =
|
||||
({ kind: "resource", args: { resource_type: "Weed", resource_id: 2 } });
|
||||
wrapper.instance().updateResource(newResource);
|
||||
expect(wrapper.state().resource).toEqual(newResource);
|
||||
expect(wrapper.state().fieldsAndValues)
|
||||
.toEqual([{ field: undefined, value: undefined }]);
|
||||
});
|
||||
|
||||
it("updates field", () => {
|
||||
const p = fakeProps();
|
||||
p.currentStep.body = undefined;
|
||||
const wrapper = mount<MarkAs>(<MarkAs {...p} />);
|
||||
expect(wrapper.state().fieldsAndValues)
|
||||
.toEqual([{ field: undefined, value: undefined }]);
|
||||
wrapper.instance().updateFieldOrValue(0)({ field: "plant_stage" });
|
||||
expect(wrapper.state().fieldsAndValues)
|
||||
.toEqual([{ field: "plant_stage", value: undefined }]);
|
||||
expect(p.dispatch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates value", () => {
|
||||
const p = fakeProps();
|
||||
p.currentStep.body && p.currentStep.body.push({
|
||||
kind: "pair", args: { label: "plant_stage", value: "planned" }
|
||||
});
|
||||
const wrapper = mount<MarkAs>(<MarkAs {...p} />);
|
||||
expect(wrapper.state().fieldsAndValues).toEqual([
|
||||
{ field: "mounted_tool_id", value: 0 },
|
||||
{ field: "plant_stage", value: "planned" },
|
||||
]);
|
||||
const callback = jest.fn();
|
||||
wrapper.instance().updateFieldOrValue(1)({ value: "planted" }, callback);
|
||||
expect(wrapper.state().fieldsAndValues).toEqual([
|
||||
{ field: "mounted_tool_id", value: 0 },
|
||||
{ field: "plant_stage", value: "planted" },
|
||||
]);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(p.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
const BaseUpdateResourceStep =
|
||||
(resource: Resource | Identifier,
|
||||
field: string,
|
||||
value: UpdateResourceValue,
|
||||
): UpdateResource => ({
|
||||
kind: "update_resource",
|
||||
args: { resource },
|
||||
body: [{ kind: "pair", args: { label: field, value } }],
|
||||
});
|
||||
|
||||
const ResourceUpdateResourceStep = (
|
||||
resourceType: resource_type,
|
||||
resourceId: number,
|
||||
field: string,
|
||||
value: UpdateResourceValue,
|
||||
): UpdateResource =>
|
||||
BaseUpdateResourceStep({
|
||||
kind: "resource",
|
||||
args: { resource_id: resourceId, resource_type: resourceType }
|
||||
}, field, value);
|
||||
|
||||
const IdentifierUpdateResourceStep = (
|
||||
label: string,
|
||||
field: string,
|
||||
value: UpdateResourceValue,
|
||||
): UpdateResource =>
|
||||
BaseUpdateResourceStep({ kind: "identifier", args: { label } }, field, value);
|
|
@ -0,0 +1,172 @@
|
|||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { FieldSelection, isCustomMetaField } from "../field_selection";
|
||||
import { FieldSelectionProps } from "../interfaces";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("<FieldSelection />", () => {
|
||||
const fakeProps = (): FieldSelectionProps => ({
|
||||
resource: { kind: "nothing", args: {} },
|
||||
field: undefined,
|
||||
resources: buildResourceIndex().index,
|
||||
update: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders disabled none field", () => {
|
||||
const p = fakeProps();
|
||||
p.field = undefined;
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([]);
|
||||
expect(wrapper.text()).toContain("field");
|
||||
expect(wrapper.text()).toContain("Select one");
|
||||
expect(wrapper.find(".reset-custom-field").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders none field", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 1 }
|
||||
};
|
||||
p.field = undefined;
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||
{ label: "Plant stage", value: "plant_stage" },
|
||||
{ label: "Custom Meta Field", value: "" },
|
||||
]);
|
||||
expect(wrapper.text()).toContain("field");
|
||||
expect(wrapper.text()).toContain("Select one");
|
||||
expect(wrapper.find(".reset-custom-field").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders custom meta field", () => {
|
||||
const p = fakeProps();
|
||||
p.field = "custom";
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(0);
|
||||
expect(wrapper.text()).toContain("field");
|
||||
expect(wrapper.find("input").props().value).toEqual("custom");
|
||||
expect(wrapper.find(".reset-custom-field").length).toEqual(1);
|
||||
});
|
||||
|
||||
it("changes custom meta field", () => {
|
||||
const p = fakeProps();
|
||||
p.field = "custom_field";
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
const input = shallow(wrapper.find("input").getElement());
|
||||
input.simulate("change", { currentTarget: { value: "1" } });
|
||||
input.simulate("blur", { currentTarget: { value: "1" } });
|
||||
expect(p.update).toHaveBeenCalledWith({ field: "1" });
|
||||
});
|
||||
|
||||
it("clears custom meta field", () => {
|
||||
const p = fakeProps();
|
||||
p.field = "custom_field";
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
wrapper.find(".reset-custom-field").simulate("click");
|
||||
expect(p.update).toHaveBeenCalledWith({
|
||||
field: undefined, value: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it("renders field list for identifier", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = { kind: "identifier", args: { label: "var" } };
|
||||
p.field = "plant_stage";
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||
{ label: "Status", value: "plant_stage" },
|
||||
{ label: "Custom Meta Field", value: "" },
|
||||
]);
|
||||
expect(wrapper.text()).toContain("field");
|
||||
expect(wrapper.text()).toContain("Status");
|
||||
expect(wrapper.find(".reset-custom-field").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders known weed field", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Weed", resource_id: 1 }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||
{ label: "Weed status", value: "plant_stage" },
|
||||
{ label: "Custom Meta Field", value: "" },
|
||||
]);
|
||||
expect(wrapper.text()).toContain("field");
|
||||
expect(wrapper.text()).toContain("Weed status");
|
||||
expect(wrapper.find(".reset-custom-field").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders known point field", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "GenericPointer", resource_id: 3 }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||
{ label: "Status", value: "plant_stage" },
|
||||
{ label: "Custom Meta Field", value: "" },
|
||||
]);
|
||||
expect(wrapper.text()).toContain("field");
|
||||
expect(wrapper.text()).toContain("Status");
|
||||
expect(wrapper.find(".reset-custom-field").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("changes known weed field", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Weed", resource_id: 1 }
|
||||
};
|
||||
p.field = undefined;
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
const select = shallow(<div>{wrapper.find("FBSelect").getElement()}</div>);
|
||||
select.find("FBSelect").simulate("change", {
|
||||
label: "", value: "plant_stage"
|
||||
});
|
||||
expect(p.update).toHaveBeenCalledWith({ field: "plant_stage" });
|
||||
});
|
||||
|
||||
it("renders known device field", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Device", resource_id: 1 }
|
||||
};
|
||||
p.field = "mounted_tool_id";
|
||||
const wrapper = mount(<FieldSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||
{ label: "Mounted Tool", value: "mounted_tool_id" },
|
||||
{ label: "Custom Meta Field", value: "" },
|
||||
]);
|
||||
expect(wrapper.text()).toContain("field");
|
||||
expect(wrapper.text()).toContain("Mounted Tool");
|
||||
expect(wrapper.find(".reset-custom-field").length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isCustomMetaField()", () => {
|
||||
it("is custom meta field", () => {
|
||||
expect(isCustomMetaField("")).toBeTruthy();
|
||||
expect(isCustomMetaField("custom")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("is not custom meta field", () => {
|
||||
expect(isCustomMetaField(undefined)).toBeFalsy();
|
||||
expect(isCustomMetaField("plant_stage")).toBeFalsy();
|
||||
expect(isCustomMetaField("mounted_tool_id")).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { ResourceSelection } from "../resource_selection";
|
||||
import { ResourceSelectionProps } from "../interfaces";
|
||||
import {
|
||||
buildResourceIndex, fakeDevice,
|
||||
} from "../../../../__test_support__/resource_index_builder";
|
||||
import { fakePlant } from "../../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("<ResourceSelection />", () => {
|
||||
const plant = fakePlant();
|
||||
plant.body.id = 1;
|
||||
|
||||
const fakeProps = (): ResourceSelectionProps => ({
|
||||
resource: { kind: "nothing", args: {} },
|
||||
resources: buildResourceIndex([plant]).index,
|
||||
updateResource: jest.fn(),
|
||||
sequenceUuid: "fake Sequence UUID",
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const p = fakeProps();
|
||||
const device = fakeDevice();
|
||||
device.body.id = 1;
|
||||
p.resources = buildResourceIndex([device]).index;
|
||||
const wrapper = mount(<ResourceSelection {...p} />);
|
||||
expect(wrapper.text()).toContain("Mark");
|
||||
expect(wrapper.text()).toContain("Select one");
|
||||
});
|
||||
|
||||
it("renders resource", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 1 }
|
||||
};
|
||||
const wrapper = mount(<ResourceSelection {...p} />);
|
||||
expect(wrapper.text()).toContain("Mark");
|
||||
expect(wrapper.text()).toContain("Strawberry plant 1 (100, 200, 0)");
|
||||
});
|
||||
|
||||
it("renders identifier", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "identifier",
|
||||
args: { label: "var" }
|
||||
};
|
||||
const wrapper = mount(<ResourceSelection {...p} />);
|
||||
expect(wrapper.text()).toContain("Mark");
|
||||
expect(wrapper.text()).toContain("Variable - Add new");
|
||||
});
|
||||
|
||||
it("renders identifier with label", () => {
|
||||
const p = fakeProps();
|
||||
p.resources.sequenceMetas["fake uuid"] = {
|
||||
parent: {
|
||||
celeryNode: {
|
||||
kind: "parameter_declaration", args: {
|
||||
label: "parent", default_value: {
|
||||
kind: "coordinate", args: { x: 0, y: 0, z: 0 }
|
||||
}
|
||||
}
|
||||
},
|
||||
dropdown: { label: "Parent", value: "parent" },
|
||||
vector: undefined,
|
||||
}
|
||||
};
|
||||
p.sequenceUuid = "fake uuid";
|
||||
p.resource = {
|
||||
kind: "identifier",
|
||||
args: { label: "parent" }
|
||||
};
|
||||
const wrapper = mount(<ResourceSelection {...p} />);
|
||||
expect(wrapper.text()).toContain("Mark");
|
||||
expect(wrapper.text()).toContain("Variable - Parent");
|
||||
});
|
||||
|
||||
it("changes resource", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ResourceSelection {...p} />);
|
||||
wrapper.find("FBSelect").simulate("change", {
|
||||
label: "", value: "1", headingId: "Plant",
|
||||
});
|
||||
expect(p.updateResource).toHaveBeenCalledWith({
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 1 }
|
||||
});
|
||||
});
|
||||
|
||||
it("changes resource to identifier", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ResourceSelection {...p} />);
|
||||
wrapper.find("FBSelect").simulate("change", {
|
||||
label: "Variable", value: "parent", headingId: "Identifier",
|
||||
});
|
||||
expect(p.updateResource).toHaveBeenCalledWith({
|
||||
kind: "identifier",
|
||||
args: { label: "parent" }
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,256 @@
|
|||
let mockDev = false;
|
||||
jest.mock("../../../../account/dev/dev_support", () => ({
|
||||
DevSettings: { futureFeaturesEnabled: () => mockDev }
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { ValueSelection } from "../value_selection";
|
||||
import { ValueSelectionProps } from "../interfaces";
|
||||
import {
|
||||
buildResourceIndex,
|
||||
} from "../../../../__test_support__/resource_index_builder";
|
||||
import {
|
||||
PLANT_STAGE_LIST,
|
||||
} from "../../../../farm_designer/plants/edit_plant_status";
|
||||
import { fakeTool } from "../../../../__test_support__/fake_state/resources";
|
||||
import { resource_type, Resource } from "farmbot";
|
||||
|
||||
describe("<ValueSelection />", () => {
|
||||
const fakeProps = (): ValueSelectionProps => ({
|
||||
resource: { kind: "nothing", args: {} },
|
||||
field: undefined,
|
||||
value: undefined,
|
||||
resources: buildResourceIndex().index,
|
||||
update: jest.fn(),
|
||||
add: jest.fn(),
|
||||
commitSelection: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders none value", () => {
|
||||
const p = fakeProps();
|
||||
p.field = undefined;
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Select one");
|
||||
});
|
||||
|
||||
it("renders custom meta value", () => {
|
||||
const p = fakeProps();
|
||||
p.field = "custom_field";
|
||||
p.value = "custom_value";
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(0);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.find("input").props().value).toEqual("custom_value");
|
||||
});
|
||||
|
||||
it("renders missing custom meta value", () => {
|
||||
const p = fakeProps();
|
||||
p.field = "custom_field";
|
||||
p.value = undefined;
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(0);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.find("input").props().value).toEqual("");
|
||||
});
|
||||
|
||||
it("changes custom meta value", () => {
|
||||
const p = fakeProps();
|
||||
p.field = "custom_field";
|
||||
p.value = "custom_value";
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
const input = shallow(wrapper.find("input").getElement());
|
||||
input.simulate("change", { currentTarget: { value: "1" } });
|
||||
input.simulate("blur", { currentTarget: { value: "1" } });
|
||||
expect(p.update).toHaveBeenCalledWith({ value: "1" },
|
||||
expect.any(Function));
|
||||
});
|
||||
|
||||
it("adds row", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
wrapper.find("label").simulate("click");
|
||||
expect(p.add).not.toHaveBeenCalled();
|
||||
mockDev = true;
|
||||
wrapper.find("label").simulate("click");
|
||||
expect(p.add).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
it("renders known plant value", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 1 }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
p.value = "planted";
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual(PLANT_STAGE_LIST());
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Planted");
|
||||
});
|
||||
|
||||
it("renders plant value", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Plant", resource_id: 1 }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
p.value = "other";
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual(PLANT_STAGE_LIST());
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("other");
|
||||
});
|
||||
|
||||
it("renders known weed value", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Weed", resource_id: 1 }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
p.value = "removed";
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||
{ label: "Removed", value: "removed" },
|
||||
]);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Removed");
|
||||
});
|
||||
|
||||
it("changes known weed value", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Weed", resource_id: 1 }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
p.value = undefined;
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
const select = shallow(<div>{wrapper.find("FBSelect").getElement()}</div>);
|
||||
select.find("FBSelect").simulate("change", {
|
||||
label: "", value: "removed"
|
||||
});
|
||||
expect(p.update).toHaveBeenCalledWith({ value: "removed" },
|
||||
expect.any(Function));
|
||||
});
|
||||
|
||||
it("renders known point value", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "GenericPointer", resource_id: 1 }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
p.value = "removed";
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||
{ label: "Removed", value: "removed" },
|
||||
]);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Removed");
|
||||
});
|
||||
|
||||
it("renders other value", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Other" as resource_type, resource_id: 1 }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
p.value = "removed";
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual(PLANT_STAGE_LIST());
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Removed");
|
||||
});
|
||||
|
||||
const TOOL_OPTIONS = [
|
||||
{ label: "None", value: 0 },
|
||||
{ label: "Trench Digging Tool", value: 14 },
|
||||
{ label: "Berry Picking Tool", value: 15 },
|
||||
];
|
||||
|
||||
const DeviceResource: Resource = {
|
||||
kind: "resource",
|
||||
args: { resource_type: "Device", resource_id: 1 }
|
||||
};
|
||||
|
||||
it("renders known tool value: not mounted", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = DeviceResource;
|
||||
p.field = "mounted_tool_id";
|
||||
p.value = 0;
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual(TOOL_OPTIONS);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("None");
|
||||
});
|
||||
|
||||
it("renders known tool value: mounted", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = DeviceResource;
|
||||
p.field = "mounted_tool_id";
|
||||
p.value = 14;
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual(TOOL_OPTIONS);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Trench Digging Tool");
|
||||
});
|
||||
|
||||
it("renders known tool value: unknown tool", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = DeviceResource;
|
||||
p.field = "mounted_tool_id";
|
||||
p.value = 123;
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual(TOOL_OPTIONS);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Unknown tool");
|
||||
});
|
||||
|
||||
it("renders known tool value: untitled tool", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = DeviceResource;
|
||||
p.field = "mounted_tool_id";
|
||||
p.value = 1;
|
||||
const tool = fakeTool();
|
||||
tool.body.id = 1;
|
||||
tool.body.name = undefined;
|
||||
p.resources = buildResourceIndex([tool]).index;
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||
{ label: "None", value: 0 },
|
||||
{ label: "Untitled tool", value: 1 },
|
||||
]);
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Untitled tool");
|
||||
});
|
||||
|
||||
it("renders known identifier value", () => {
|
||||
const p = fakeProps();
|
||||
p.resource = {
|
||||
kind: "identifier", args: { label: "var" }
|
||||
};
|
||||
p.field = "plant_stage";
|
||||
p.value = "planted";
|
||||
const wrapper = mount(<ValueSelection {...p} />);
|
||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||
expect(wrapper.find("FBSelect").props().list).toEqual(PLANT_STAGE_LIST());
|
||||
expect(wrapper.text()).toContain("as");
|
||||
expect(wrapper.text()).toContain("Planted");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,119 @@
|
|||
import * as React from "react";
|
||||
import { editStep } from "../../../api/crud";
|
||||
import { Row, Col } from "../../../ui";
|
||||
import { StepWrapper, StepHeader, StepContent } from "../../step_ui/index";
|
||||
import { ToolTips } from "../../../constants";
|
||||
import { UpdateResource, Resource, Identifier } from "farmbot";
|
||||
import { MarkAsState, MarkAsProps, FieldAndValue } from "./interfaces";
|
||||
import { ResourceSelection } from "./resource_selection";
|
||||
import { FieldSelection } from "./field_selection";
|
||||
import { ValueSelection } from "./value_selection";
|
||||
import { isUndefined } from "lodash";
|
||||
import { NOTHING_SELECTED } from "../../locals_list/handle_select";
|
||||
|
||||
export class MarkAs extends React.Component<MarkAsProps, MarkAsState> {
|
||||
state: MarkAsState = {
|
||||
resource: this.step.args.resource,
|
||||
fieldsAndValues: this.step.body?.length
|
||||
? this.step.body.map(pair =>
|
||||
({ field: pair.args.label, value: pair.args.value }))
|
||||
: [{ field: undefined, value: undefined }],
|
||||
};
|
||||
|
||||
get step() { return this.props.currentStep; }
|
||||
|
||||
editStep = (executor: (s: UpdateResource) => void) =>
|
||||
this.props.dispatch(editStep({
|
||||
step: this.step,
|
||||
index: this.props.index,
|
||||
sequence: this.props.currentSequence,
|
||||
executor,
|
||||
}));
|
||||
|
||||
resetStep = () =>
|
||||
this.editStep(s => {
|
||||
s.args = { resource: NOTHING_SELECTED };
|
||||
s.body = [];
|
||||
});
|
||||
|
||||
commitSelection = () => {
|
||||
const { resource, fieldsAndValues } = this.state;
|
||||
this.editStep(s => {
|
||||
if (fieldsAndValues.length > 0 && resource.kind != "nothing") {
|
||||
s.args = { resource };
|
||||
s.body = [];
|
||||
fieldsAndValues.map(({ field, value }) => {
|
||||
if (s.body && !isUndefined(field) && !isUndefined(value)) {
|
||||
s.body.push({ kind: "pair", args: { label: field, value: value } });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
updateResource = (resource: Resource | Identifier) => {
|
||||
this.setState({
|
||||
resource,
|
||||
fieldsAndValues: [{ field: undefined, value: undefined }],
|
||||
});
|
||||
this.resetStep();
|
||||
};
|
||||
|
||||
updateFieldOrValue = (index: number) =>
|
||||
(update: Partial<FieldAndValue>, callback?: () => void) => {
|
||||
const { fieldsAndValues } = this.state;
|
||||
const old = fieldsAndValues[index];
|
||||
fieldsAndValues[index] = { ...old, ...update };
|
||||
this.setState({ fieldsAndValues: fieldsAndValues }, callback);
|
||||
if (isUndefined(update.value) && fieldsAndValues.length < 2) {
|
||||
this.resetStep();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const commonProps = {
|
||||
key: JSON.stringify(this.state)
|
||||
+ JSON.stringify(this.props.currentSequence.body.args.locals),
|
||||
resource: this.state.resource,
|
||||
resources: this.props.resources,
|
||||
};
|
||||
const className = "update-resource-step";
|
||||
return <StepWrapper>
|
||||
<StepHeader
|
||||
className={className}
|
||||
helpText={ToolTips.MARK_AS}
|
||||
currentSequence={this.props.currentSequence}
|
||||
currentStep={this.props.currentStep}
|
||||
dispatch={this.props.dispatch}
|
||||
index={this.props.index}
|
||||
confirmStepDeletion={this.props.confirmStepDeletion} />
|
||||
<StepContent className={className}>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<ResourceSelection {...commonProps}
|
||||
sequenceUuid={this.props.currentSequence.uuid}
|
||||
updateResource={this.updateResource} />
|
||||
</Col>
|
||||
</Row>
|
||||
{this.state.fieldsAndValues.map((fieldAndValue, index) =>
|
||||
<div className={"update-resource-pair"} key={index}>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<FieldSelection {...commonProps}
|
||||
field={fieldAndValue.field}
|
||||
update={this.updateFieldOrValue(index)} />
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<ValueSelection {...commonProps}
|
||||
field={fieldAndValue.field}
|
||||
value={fieldAndValue.value}
|
||||
update={this.updateFieldOrValue(index)}
|
||||
add={this.updateFieldOrValue(this.state.fieldsAndValues.length)}
|
||||
commitSelection={this.commitSelection} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>)}
|
||||
</StepContent>
|
||||
</StepWrapper>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import * as React from "react";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { FBSelect, DropDownItem, BlurableInput } from "../../../ui";
|
||||
import { Resource, Identifier, Nothing } from "farmbot";
|
||||
import { isUndefined } from "lodash";
|
||||
import { FieldSelectionProps, CustomFieldSelectionProps } from "./interfaces";
|
||||
|
||||
export const FieldSelection = (props: FieldSelectionProps) =>
|
||||
<div className={"update-resource-step-field"}>
|
||||
<label>{t("field")}</label>
|
||||
{(isCustomMetaField(props.field) && !isUndefined(props.field))
|
||||
? <CustomMetaField {...props} field={props.field} />
|
||||
: <KnownFieldSelection {...props} />}
|
||||
</div>;
|
||||
|
||||
const KnownFieldSelection = (props: FieldSelectionProps) =>
|
||||
<FBSelect
|
||||
extraClass={props.resource.kind == "nothing" ? "disabled" : ""}
|
||||
list={props.resource.kind == "nothing"
|
||||
? []
|
||||
: fieldList(props.resource)
|
||||
.concat([{ label: t("Custom Meta Field"), value: "" }])}
|
||||
onChange={ddi => props.update({
|
||||
field: "" + ddi.value,
|
||||
value: undefined
|
||||
})}
|
||||
allowEmpty={false}
|
||||
selectedItem={getSelectedField(
|
||||
props.resource, knownField(props.field))} />;
|
||||
|
||||
const CustomMetaField = (props: CustomFieldSelectionProps) =>
|
||||
<div className="custom-meta-field">
|
||||
<BlurableInput type="text" name="field"
|
||||
onCommit={e => props.update({
|
||||
field: e.currentTarget.value,
|
||||
value: undefined
|
||||
})}
|
||||
allowEmpty={true}
|
||||
value={props.field} />
|
||||
<i className={"reset-custom-field fa fa-undo"}
|
||||
title={t("reset")}
|
||||
onClick={() => props.update({ field: undefined, value: undefined })} />
|
||||
</div>;
|
||||
|
||||
export enum KnownField {
|
||||
plant_stage = "plant_stage",
|
||||
mounted_tool_id = "mounted_tool_id",
|
||||
}
|
||||
|
||||
const isKnownField = (x: string | undefined): x is KnownField =>
|
||||
!!(x && Object.keys(KnownField).includes(x));
|
||||
|
||||
export const knownField =
|
||||
(field: string | undefined): KnownField | undefined =>
|
||||
isKnownField(field) ? field : undefined;
|
||||
|
||||
export const isCustomMetaField = (field: string | undefined): boolean =>
|
||||
!(isUndefined(field) || knownField(field));
|
||||
|
||||
const fieldList = (resource: Resource | Identifier) => {
|
||||
if (resource.kind == "identifier") {
|
||||
return [{ label: t("Status"), value: "plant_stage" }];
|
||||
}
|
||||
switch (resource.args.resource_type) {
|
||||
case "Device":
|
||||
return [{ label: t("Mounted Tool"), value: "mounted_tool_id" }];
|
||||
case "Weed":
|
||||
return [{ label: t("Weed status"), value: "plant_stage" }];
|
||||
case "GenericPointer":
|
||||
return [{ label: t("Status"), value: "plant_stage" }];
|
||||
default:
|
||||
return [{ label: t("Plant stage"), value: "plant_stage" }];
|
||||
}
|
||||
};
|
||||
|
||||
const getSelectedField = (
|
||||
resource: Resource | Identifier | Nothing,
|
||||
field: KnownField | undefined,
|
||||
): DropDownItem => {
|
||||
if (isUndefined(field) || resource.kind == "nothing") {
|
||||
return { label: t("Select one"), value: "" };
|
||||
}
|
||||
if (resource.kind == "identifier") {
|
||||
return { label: t("Status"), value: "plant_stage" };
|
||||
}
|
||||
const resourceType = resource.args.resource_type;
|
||||
switch (field) {
|
||||
case KnownField.mounted_tool_id:
|
||||
return { label: t("Mounted Tool"), value: "tool" };
|
||||
case KnownField.plant_stage:
|
||||
if (resourceType == "Weed") {
|
||||
return { label: t("Weed status"), value: "plant_stage" };
|
||||
}
|
||||
if (resourceType == "GenericPointer") {
|
||||
return { label: t("Status"), value: "plant_stage" };
|
||||
}
|
||||
return { label: t("Plant stage"), value: "plant_stage" };
|
||||
}
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
import { ResourceIndex, UUID } from "../../../resources/interfaces";
|
||||
import {
|
||||
UpdateResource, TaggedSequence, Resource, Identifier, Nothing, Pair,
|
||||
} from "farmbot";
|
||||
import { KnownField } from "./field_selection";
|
||||
|
||||
export interface MarkAsProps {
|
||||
currentSequence: TaggedSequence;
|
||||
currentStep: UpdateResource;
|
||||
dispatch: Function;
|
||||
index: number;
|
||||
resources: ResourceIndex;
|
||||
confirmStepDeletion: boolean;
|
||||
}
|
||||
|
||||
export type UpdateResourceValue = Pair["args"]["value"];
|
||||
|
||||
export interface FieldAndValue {
|
||||
field: string | undefined;
|
||||
value: UpdateResourceValue | undefined;
|
||||
}
|
||||
|
||||
export interface MarkAsState {
|
||||
resource: Resource | Identifier | Nothing;
|
||||
fieldsAndValues: FieldAndValue[];
|
||||
}
|
||||
|
||||
export interface GetSelectedValueProps {
|
||||
resource: Resource | Identifier | Nothing;
|
||||
field: KnownField | undefined;
|
||||
value: UpdateResourceValue | undefined;
|
||||
resourceIndex: ResourceIndex;
|
||||
}
|
||||
|
||||
interface SelectionPropsBase {
|
||||
resource: Resource | Identifier | Nothing;
|
||||
resources: ResourceIndex;
|
||||
}
|
||||
|
||||
export interface ResourceSelectionProps extends SelectionPropsBase {
|
||||
updateResource(resource: Resource | Identifier): void;
|
||||
sequenceUuid: UUID;
|
||||
}
|
||||
|
||||
type UpdateFieldOrValue =
|
||||
(update: Partial<FieldAndValue>, callback?: () => void) => void;
|
||||
|
||||
export interface FieldSelectionProps extends SelectionPropsBase {
|
||||
field: string | undefined;
|
||||
update: UpdateFieldOrValue;
|
||||
}
|
||||
|
||||
export interface CustomFieldSelectionProps extends SelectionPropsBase {
|
||||
field: string;
|
||||
update: UpdateFieldOrValue;
|
||||
}
|
||||
|
||||
export interface ValueSelectionProps extends SelectionPropsBase {
|
||||
field: string | undefined;
|
||||
value: UpdateResourceValue | undefined;
|
||||
update: UpdateFieldOrValue;
|
||||
add: UpdateFieldOrValue;
|
||||
commitSelection(): void;
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import * as React from "react";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { FBSelect } from "../../../ui";
|
||||
import {
|
||||
resource_type as RESOURCE_TYPE, Identifier, Resource, Nothing,
|
||||
} from "farmbot";
|
||||
import { ResourceSelectionProps } from "./interfaces";
|
||||
import { ResourceIndex, UUID } from "../../../resources/interfaces";
|
||||
import { DropDownItem } from "../../../ui/fb_select";
|
||||
import {
|
||||
selectAllPoints, maybeGetDevice, findPointerByTypeAndId,
|
||||
} from "../../../resources/selectors";
|
||||
import { formatPoint } from "../../locals_list/location_form_list";
|
||||
import {
|
||||
maybeFindVariable, SequenceMeta,
|
||||
} from "../../../resources/sequence_meta";
|
||||
|
||||
export const ResourceSelection = (props: ResourceSelectionProps) =>
|
||||
<div className={"update-resource-step-resource"}>
|
||||
<label>{t("Mark")}</label>
|
||||
<FBSelect
|
||||
list={resourceList(props.resources, props.sequenceUuid)}
|
||||
onChange={ddi => props.updateResource(prepareResource(ddi))}
|
||||
selectedItem={getSelectedResource(
|
||||
props.resource, props.resources, props.sequenceUuid)} />
|
||||
</div>;
|
||||
|
||||
const prepareResource = (ddi: DropDownItem): Resource | Identifier => {
|
||||
switch (ddi.headingId) {
|
||||
case "Identifier":
|
||||
return { kind: "identifier", args: { label: "" + ddi.value } };
|
||||
default:
|
||||
return {
|
||||
kind: "resource",
|
||||
args: {
|
||||
resource_type: ddi.headingId as RESOURCE_TYPE,
|
||||
resource_id: parseInt("" + ddi.value)
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const resourceList =
|
||||
(resources: ResourceIndex, sequenceUuid: UUID): DropDownItem[] => {
|
||||
const deviceId = maybeGetDevice(resources)?.body.id || 0;
|
||||
const points = selectAllPoints(resources).filter(p => !!p.body.id);
|
||||
const mapPoints = points.filter(p => p.body.pointer_type == "GenericPointer");
|
||||
const weeds = points.filter(p => p.body.pointer_type == "Weed");
|
||||
const plants = points.filter(p => p.body.pointer_type == "Plant");
|
||||
const headingCommon = { heading: true, value: 0 };
|
||||
const varLabel = resourceVariableLabel(maybeFindVariable(
|
||||
"parent", resources, sequenceUuid));
|
||||
return [
|
||||
{ headingId: "Identifier", label: varLabel, value: "parent" },
|
||||
{ headingId: "Device", label: t("Device"), ...headingCommon },
|
||||
{ headingId: "Device", label: t("Tool Mount"), value: deviceId },
|
||||
{ headingId: "Plant", label: t("Plants"), ...headingCommon },
|
||||
...plants.map(formatPoint),
|
||||
{ headingId: "GenericPointer", label: t("Points"), ...headingCommon },
|
||||
...mapPoints.map(formatPoint),
|
||||
{ headingId: "Weed", label: t("Weeds"), ...headingCommon },
|
||||
...weeds.map(formatPoint),
|
||||
];
|
||||
};
|
||||
|
||||
const getSelectedResource = (
|
||||
resource: Resource | Identifier | Nothing,
|
||||
resources: ResourceIndex,
|
||||
sequenceUuid: UUID,
|
||||
): DropDownItem => {
|
||||
switch (resource.kind) {
|
||||
case "resource":
|
||||
const { resource_type, resource_id } = resource.args;
|
||||
if (resource_type == "Device") {
|
||||
return { label: t("Tool Mount"), value: resource_id };
|
||||
}
|
||||
return formatPoint(
|
||||
findPointerByTypeAndId(resources, resource_type, resource_id));
|
||||
case "identifier":
|
||||
const variable =
|
||||
maybeFindVariable(resource.args.label, resources, sequenceUuid);
|
||||
return {
|
||||
label: resourceVariableLabel(variable),
|
||||
value: resource.args.label,
|
||||
};
|
||||
case "nothing": return { label: t("Select one"), value: "" };
|
||||
}
|
||||
};
|
||||
|
||||
const resourceVariableLabel = (variable: SequenceMeta | undefined) =>
|
||||
`${t("Variable")} - ${variable?.dropdown.label || t("Add new")}`;
|
|
@ -0,0 +1,93 @@
|
|||
import * as React from "react";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { FBSelect, BlurableInput } from "../../../ui";
|
||||
import { isUndefined } from "lodash";
|
||||
import { ValueSelectionProps, GetSelectedValueProps } from "./interfaces";
|
||||
import { Identifier, Resource } from "farmbot";
|
||||
import { DropDownItem } from "../../../ui";
|
||||
import { ResourceIndex } from "../../../resources/interfaces";
|
||||
import { selectAllTools, maybeFindToolById } from "../../../resources/selectors";
|
||||
import {
|
||||
PLANT_STAGE_LIST, PLANT_STAGE_DDI_LOOKUP,
|
||||
} from "../../../farm_designer/plants/edit_plant_status";
|
||||
import { isCustomMetaField, KnownField, knownField } from "./field_selection";
|
||||
import { DevSettings } from "../../../account/dev/dev_support";
|
||||
|
||||
export const ValueSelection = (props: ValueSelectionProps) =>
|
||||
<div className={"update-resource-step-value"}>
|
||||
<label onClick={() => DevSettings.futureFeaturesEnabled() && props.add({})}>
|
||||
{t("as")}
|
||||
</label>
|
||||
{isCustomMetaField(props.field)
|
||||
? <CustomMetaValue {...props} />
|
||||
: <KnownValue {...props} />}
|
||||
</div>;
|
||||
|
||||
const KnownValue = (props: ValueSelectionProps) =>
|
||||
<FBSelect
|
||||
extraClass={isUndefined(props.field) ? "disabled" : ""}
|
||||
list={props.resource.kind == "nothing"
|
||||
? []
|
||||
: valuesList(props.resource, props.resources)}
|
||||
onChange={ddi => {
|
||||
props.update({ value: ddi.value },
|
||||
props.commitSelection);
|
||||
}}
|
||||
selectedItem={getSelectedValue({
|
||||
resourceIndex: props.resources,
|
||||
resource: props.resource,
|
||||
field: knownField(props.field),
|
||||
value: props.value,
|
||||
})} />;
|
||||
|
||||
const CustomMetaValue = (props: ValueSelectionProps) =>
|
||||
<div className="custom-meta-field">
|
||||
<BlurableInput type="text" name="value"
|
||||
value={isUndefined(props.value) ? "" : "" + props.value}
|
||||
onCommit={e => {
|
||||
props.update({ value: e.currentTarget.value },
|
||||
props.commitSelection);
|
||||
}} />
|
||||
</div>;
|
||||
|
||||
const valuesList = (
|
||||
resource: Resource | Identifier,
|
||||
resources: ResourceIndex): DropDownItem[] => {
|
||||
const stepResourceType =
|
||||
resource.kind == "identifier" ? undefined : resource.args.resource_type;
|
||||
switch (stepResourceType) {
|
||||
case "Device": return [
|
||||
{ label: t("None"), value: 0 },
|
||||
...selectAllTools(resources).filter(x => !!x.body.id)
|
||||
.map(x => ({ toolName: x.body.name, toolId: x.body.id }))
|
||||
.map(({ toolName, toolId }:
|
||||
{ toolName: string | undefined, toolId: number }) =>
|
||||
({ label: toolName || t("Untitled tool"), value: toolId })),
|
||||
];
|
||||
case "GenericPointer": return [{ label: t("Removed"), value: "removed" }];
|
||||
case "Weed": return [{ label: t("Removed"), value: "removed" }];
|
||||
case "Plant":
|
||||
default: return PLANT_STAGE_LIST();
|
||||
}
|
||||
};
|
||||
|
||||
const getSelectedValue = (props: GetSelectedValueProps): DropDownItem => {
|
||||
if (isUndefined(props.field) || isUndefined(props.value)
|
||||
|| props.resource.kind == "nothing") {
|
||||
return { label: t("Select one"), value: "" };
|
||||
}
|
||||
switch (props.field) {
|
||||
case KnownField.mounted_tool_id:
|
||||
const toolId = parseInt("" + props.value);
|
||||
if (toolId == 0) { return { label: t("None"), value: 0 }; }
|
||||
const tool = maybeFindToolById(props.resourceIndex, toolId);
|
||||
if (!tool) { return { label: t("Unknown tool"), value: toolId }; }
|
||||
return {
|
||||
label: tool.body.name || t("Untitled tool"),
|
||||
value: toolId
|
||||
};
|
||||
case KnownField.plant_stage:
|
||||
return PLANT_STAGE_DDI_LOOKUP()["" + props.value]
|
||||
|| { label: "" + props.value, value: "" + props.value };
|
||||
}
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { StepParams } from "../interfaces";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { Content } from "../../constants";
|
||||
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { ALLOWED_PACKAGES, SequenceBodyItem, Reboot } from "farmbot";
|
||||
|
@ -44,7 +44,7 @@ export function TileReboot(props: StepParams) {
|
|||
return <StepWrapper>
|
||||
<StepHeader
|
||||
className={className}
|
||||
helpText={ToolTips.REBOOT}
|
||||
helpText={Content.RESTART_FARMBOT}
|
||||
currentSequence={currentSequence}
|
||||
currentStep={currentStep}
|
||||
dispatch={dispatch}
|
||||
|
@ -52,7 +52,7 @@ export function TileReboot(props: StepParams) {
|
|||
confirmStepDeletion={props.confirmStepDeletion} />
|
||||
<StepContent className={className}>
|
||||
<p>
|
||||
{t(ToolTips.REBOOT)}
|
||||
{t(Content.REBOOT_STEP)}
|
||||
</p>
|
||||
{/* <StepRadio
|
||||
choices={Object.keys(PACKAGE_CHOICES())}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import * as React from "react";
|
||||
import { StepParams } from "../interfaces";
|
||||
import { Content } from "../../constants";
|
||||
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export function TileShutdown(props: StepParams) {
|
||||
const { dispatch, currentStep, index, currentSequence } = props;
|
||||
const className = "shutdown-step";
|
||||
return <StepWrapper>
|
||||
<StepHeader
|
||||
className={className}
|
||||
helpText={Content.SHUTDOWN_FARMBOT}
|
||||
currentSequence={currentSequence}
|
||||
currentStep={currentStep}
|
||||
dispatch={dispatch}
|
||||
index={index}
|
||||
confirmStepDeletion={props.confirmStepDeletion} />
|
||||
<StepContent className={className}>
|
||||
<p>{t(Content.SHUTDOWN_STEP)}</p>
|
||||
</StepContent>
|
||||
</StepWrapper>;
|
||||
}
|
|
@ -104,10 +104,19 @@ export enum FbosVersionFallback {
|
|||
NULL = "0.0.0",
|
||||
}
|
||||
|
||||
const fallbackData: MinOsFeatureLookup = {
|
||||
[Feature.api_farmware_env]: "8.0.0",
|
||||
[Feature.api_farmware_installations]: "8.0.0",
|
||||
[Feature.criteria_groups]: "9.2.2",
|
||||
[Feature.update_resource]: MinVersionOverride.NEVER,
|
||||
[Feature.boot_sequence]: MinVersionOverride.NEVER,
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether a feature should be displayed based on
|
||||
* the user's current FBOS version. Min FBOS version feature data is pulled
|
||||
* from an external source to allow App and FBOS development flexibility.
|
||||
* Device-less accounts can use features compatible with supported versions.
|
||||
*
|
||||
* @param current installed OS version string to compare against data ("0.0.0")
|
||||
* @param lookupData min req versions data, for example {"feature": "1.0.0"}
|
||||
|
@ -120,7 +129,7 @@ export function createShouldDisplayFn(
|
|||
const fallback = globalConfig.FBOS_END_OF_LIFE_VERSION ||
|
||||
FbosVersionFallback.NULL;
|
||||
const target = override || current || fallback;
|
||||
const table = lookupData || {};
|
||||
const table = lookupData || fallbackData;
|
||||
const min = table[feature] || MinVersionOverride.NEVER;
|
||||
switch (semverCompare(target, min)) {
|
||||
case SemverResult.LEFT_IS_GREATER:
|
||||
|
|
|
@ -44,7 +44,9 @@ end
|
|||
def get_base_branch(pull_data)
|
||||
current_branch = BASE_BRANCHES.empty? ||
|
||||
BASE_BRANCHES.include?(CURRENT_BRANCH) ? CURRENT_BRANCH : "staging"
|
||||
pull_data.dig("base", "ref") || current_branch
|
||||
provided_base_branch =
|
||||
CURRENT_BRANCH.start_with?("master-hotfix/") ? "master" : nil;
|
||||
pull_data.dig("base", "ref") || provided_base_branch || current_branch
|
||||
end
|
||||
|
||||
# Gather relevant coverage data.
|
||||
|
|
30
package.json
30
package.json
|
@ -25,35 +25,35 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.9.0",
|
||||
"@blueprintjs/core": "3.25.0",
|
||||
"@blueprintjs/datetime": "3.16.0",
|
||||
"@blueprintjs/select": "3.12.1",
|
||||
"@blueprintjs/core": "3.26.0",
|
||||
"@blueprintjs/datetime": "3.16.1",
|
||||
"@blueprintjs/select": "3.12.2",
|
||||
"@types/enzyme": "3.10.5",
|
||||
"@types/jest": "25.2.1",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/markdown-it": "10.0.0",
|
||||
"@types/lodash": "4.14.150",
|
||||
"@types/markdown-it": "10.0.1",
|
||||
"@types/moxios": "0.4.9",
|
||||
"@types/node": "13.11.1",
|
||||
"@types/node": "13.13.4",
|
||||
"@types/promise-timeout": "1.3.0",
|
||||
"@types/react": "16.9.34",
|
||||
"@types/react-color": "3.0.1",
|
||||
"@types/react-dom": "16.9.6",
|
||||
"@types/react-dom": "16.9.7",
|
||||
"@types/react-redux": "7.1.7",
|
||||
"axios": "0.19.2",
|
||||
"boxed_value": "1.0.0",
|
||||
"browser-speech": "1.1.1",
|
||||
"coveralls": "3.0.11",
|
||||
"coveralls": "3.1.0",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.2",
|
||||
"farmbot": "10.0.0-rc1",
|
||||
"i18next": "19.4.1",
|
||||
"i18next": "19.4.4",
|
||||
"install": "0.13.0",
|
||||
"lodash": "4.17.15",
|
||||
"markdown-it": "10.0.0",
|
||||
"markdown-it-emoji": "1.4.0",
|
||||
"moment": "2.24.0",
|
||||
"moxios": "0.4.0",
|
||||
"mqtt": "3.0.0",
|
||||
"mqtt": "4.0.0",
|
||||
"npm": "6.14.4",
|
||||
"parcel-bundler": "1.12.4",
|
||||
"promise-timeout": "1.3.0",
|
||||
|
@ -71,19 +71,19 @@
|
|||
"redux-thunk": "2.3.0",
|
||||
"sass-lint": "1.13.1",
|
||||
"takeme": "0.11.3",
|
||||
"ts-jest": "25.3.1",
|
||||
"ts-jest": "25.4.0",
|
||||
"ts-lint": "4.5.1",
|
||||
"tslint": "6.1.1",
|
||||
"tslint": "6.1.2",
|
||||
"typescript": "3.8.3",
|
||||
"which": "2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "25.3.0",
|
||||
"jest-cli": "25.3.0",
|
||||
"jest": "25.4.0",
|
||||
"jest-cli": "25.4.0",
|
||||
"jest-junit": "10.0.0",
|
||||
"jest-skipped-reporter": "0.0.5",
|
||||
"jshint": "2.11.0",
|
||||
"madge": "3.8.0",
|
||||
"sass": "1.26.3"
|
||||
"sass": "1.26.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,7 @@ describe Api::PointsController do
|
|||
get :index
|
||||
expect(response.status).to eq(200)
|
||||
expect(json.length).to eq(3)
|
||||
json.map { |json| expect(json[:created_at]).to eq(json[:planted_at]) }
|
||||
end
|
||||
it "lists all tool slots" do
|
||||
Point.destroy_all
|
||||
|
|
Loading…
Reference in New Issue