fix group edit mode actions
parent
fd867fca70
commit
6305e41e8f
|
@ -1,16 +1,13 @@
|
||||||
jest.mock("../history",
|
jest.mock("../history", () => ({ push: jest.fn() }));
|
||||||
() => ({ push: jest.fn() }));
|
|
||||||
const mockSyncThunk = jest.fn();
|
const mockSyncThunk = jest.fn();
|
||||||
jest.mock("../devices/actions",
|
jest.mock("../devices/actions", () => ({ sync: () => mockSyncThunk }));
|
||||||
() => ({ sync: () => mockSyncThunk }));
|
jest.mock("../farm_designer/map/actions", () => ({ unselectPlant: jest.fn() }));
|
||||||
jest.mock("../farm_designer/actions",
|
|
||||||
() => ({ unselectPlant: jest.fn() }));
|
|
||||||
|
|
||||||
import { HotKeys } from "../hotkeys";
|
import { HotKeys } from "../hotkeys";
|
||||||
import { betterCompact } from "../util";
|
import { betterCompact } from "../util";
|
||||||
import { push } from "../history";
|
import { push } from "../history";
|
||||||
import { sync } from "../devices/actions";
|
import { sync } from "../devices/actions";
|
||||||
import { unselectPlant } from "../farm_designer/actions";
|
import { unselectPlant } from "../farm_designer/map/actions";
|
||||||
|
|
||||||
describe("hotkeys", () => {
|
describe("hotkeys", () => {
|
||||||
it("has key bindings", () => {
|
it("has key bindings", () => {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import {
|
import { NameInputBox, PinDropdown, ModeDropdown } from "../pin_form_fields";
|
||||||
NameInputBox, PinDropdown, ModeDropdown, DeleteButton,
|
|
||||||
} from "../pin_form_fields";
|
|
||||||
import { fakeSensor } from "../../__test_support__/fake_state/resources";
|
import { fakeSensor } from "../../__test_support__/fake_state/resources";
|
||||||
import { Actions } from "../../constants";
|
import { Actions } from "../../constants";
|
||||||
import { FBSelect } from "../../ui";
|
import { FBSelect } from "../../ui";
|
||||||
|
@ -64,17 +62,3 @@ describe("<ModeDropdown />", () => {
|
||||||
expectedPayload({ mode: 0 }));
|
expectedPayload({ mode: 0 }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("<DeleteButton />", () => {
|
|
||||||
const fakeProps = () => ({
|
|
||||||
dispatch: jest.fn(() => Promise.resolve()),
|
|
||||||
uuid: "resource uuid",
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deletes resource", () => {
|
|
||||||
const p = fakeProps();
|
|
||||||
const wrapper = shallow(<DeleteButton {...p} />);
|
|
||||||
wrapper.find("button").simulate("click");
|
|
||||||
expect(p.dispatch).toHaveBeenCalledWith(expect.any(Function));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ import * as React from "react";
|
||||||
import { PeripheralFormProps } from "./interfaces";
|
import { PeripheralFormProps } from "./interfaces";
|
||||||
import { sortResourcesById } from "../../util";
|
import { sortResourcesById } from "../../util";
|
||||||
import { Row, Col } from "../../ui";
|
import { Row, Col } from "../../ui";
|
||||||
import { NameInputBox, PinDropdown, DeleteButton } from "../pin_form_fields";
|
import { DeleteButton } from "../../ui/delete_button";
|
||||||
|
import { NameInputBox, PinDropdown } from "../pin_form_fields";
|
||||||
|
|
||||||
export const PeripheralForm = (props: PeripheralFormProps) =>
|
export const PeripheralForm = (props: PeripheralFormProps) =>
|
||||||
<div className="peripheral-form">
|
<div className="peripheral-form">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { destroy, edit } from "../api/crud";
|
import { edit } from "../api/crud";
|
||||||
import { FBSelect } from "../ui";
|
import { FBSelect } from "../ui";
|
||||||
import {
|
import {
|
||||||
pinDropdowns
|
pinDropdowns
|
||||||
|
@ -7,9 +7,8 @@ import {
|
||||||
import { PIN_MODES } from "../sequences/step_tiles/tile_pin_support";
|
import { PIN_MODES } from "../sequences/step_tiles/tile_pin_support";
|
||||||
import { t } from "../i18next_wrapper";
|
import { t } from "../i18next_wrapper";
|
||||||
import { TaggedPeripheral, TaggedSensor } from "farmbot";
|
import { TaggedPeripheral, TaggedSensor } from "farmbot";
|
||||||
import { UUID } from "../resources/interfaces";
|
|
||||||
import { isNumber } from "lodash";
|
import { isNumber } from "lodash";
|
||||||
import { omit } from "lodash";
|
|
||||||
const MODES = (): { [s: string]: string } => ({
|
const MODES = (): { [s: string]: string } => ({
|
||||||
0: t("Digital"),
|
0: t("Digital"),
|
||||||
1: t("Analog")
|
1: t("Analog")
|
||||||
|
@ -58,35 +57,3 @@ export const ModeDropdown = (props: ModeDropdownProps) =>
|
||||||
}))}
|
}))}
|
||||||
selectedItem={{ label: MODES()[props.value], value: props.value }}
|
selectedItem={{ label: MODES()[props.value], value: props.value }}
|
||||||
list={PIN_MODES()} />;
|
list={PIN_MODES()} />;
|
||||||
|
|
||||||
interface ButtonCustomProps {
|
|
||||||
dispatch: Function;
|
|
||||||
uuid: UUID;
|
|
||||||
children?: React.ReactChild
|
|
||||||
onDestroy?: Function;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ButtonHtmlProps =
|
|
||||||
React.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
||||||
|
|
||||||
type DeleteButtonProps =
|
|
||||||
ButtonCustomProps & ButtonHtmlProps;
|
|
||||||
|
|
||||||
/** Unfortunately, React will trigger a runtime
|
|
||||||
* warning if we pass extra props to HTML elements */
|
|
||||||
const OMIT_THESE: Record<keyof ButtonCustomProps, true> = {
|
|
||||||
"dispatch": true,
|
|
||||||
"uuid": true,
|
|
||||||
"children": true,
|
|
||||||
"onDestroy": true,
|
|
||||||
};
|
|
||||||
export const DeleteButton = (props: DeleteButtonProps) =>
|
|
||||||
<button
|
|
||||||
{...omit(props, Object.keys(OMIT_THESE))}
|
|
||||||
className="red fb-button del-button"
|
|
||||||
title={t("Delete")}
|
|
||||||
onClick={() =>
|
|
||||||
props.dispatch(destroy(props.uuid))
|
|
||||||
.then(props.onDestroy || (() => { }))}>
|
|
||||||
{props.children || <i className="fa fa-times" />}
|
|
||||||
</button>;
|
|
||||||
|
|
|
@ -2,9 +2,8 @@ import * as React from "react";
|
||||||
import { SensorFormProps } from "./interfaces";
|
import { SensorFormProps } from "./interfaces";
|
||||||
import { sortResourcesById } from "../../util";
|
import { sortResourcesById } from "../../util";
|
||||||
import { Row, Col } from "../../ui";
|
import { Row, Col } from "../../ui";
|
||||||
import {
|
import { DeleteButton } from "../../ui/delete_button";
|
||||||
NameInputBox, PinDropdown, ModeDropdown, DeleteButton
|
import { NameInputBox, PinDropdown, ModeDropdown } from "../pin_form_fields";
|
||||||
} from "../pin_form_fields";
|
|
||||||
|
|
||||||
export const SensorForm = (props: SensorFormProps) =>
|
export const SensorForm = (props: SensorFormProps) =>
|
||||||
<div className="sensor-form">
|
<div className="sensor-form">
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
let mockPath = "/app/designer/plants";
|
|
||||||
jest.mock("../../history", () => ({
|
|
||||||
history: { push: jest.fn() },
|
|
||||||
getPathArray: jest.fn(() => { return mockPath.split("/"); })
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("../../api/crud", () => ({
|
|
||||||
edit: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
import { movePlant, closePlantInfo, setDragIcon } from "../actions";
|
|
||||||
import { MovePlantProps } from "../interfaces";
|
|
||||||
import { fakePlant } from "../../__test_support__/fake_state/resources";
|
|
||||||
import { edit } from "../../api/crud";
|
|
||||||
import { Actions } from "../../constants";
|
|
||||||
import { DEFAULT_ICON, svgToUrl } from "../../open_farm/icons";
|
|
||||||
import { history } from "../../history";
|
|
||||||
|
|
||||||
describe("movePlant", () => {
|
|
||||||
function movePlantTest(
|
|
||||||
caseDescription: string,
|
|
||||||
attempted: { x: number, y: number },
|
|
||||||
expected: { x: number, y: number }) {
|
|
||||||
it(`restricts plant to grid area: ${caseDescription}`, () => {
|
|
||||||
const payload: MovePlantProps = {
|
|
||||||
deltaX: attempted.x,
|
|
||||||
deltaY: attempted.y,
|
|
||||||
plant: fakePlant(),
|
|
||||||
gridSize: { x: 3000, y: 1500 }
|
|
||||||
};
|
|
||||||
movePlant(payload);
|
|
||||||
expect(edit).toHaveBeenCalledWith(
|
|
||||||
// Old plant
|
|
||||||
expect.objectContaining({
|
|
||||||
body: expect.objectContaining({
|
|
||||||
x: 100, y: 200
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
// Update
|
|
||||||
expect.objectContaining({
|
|
||||||
x: expected.x, y: expected.y
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
movePlantTest("within bounds", { x: 1, y: 2 }, { x: 101, y: 202 });
|
|
||||||
movePlantTest("too high", { x: 10000, y: 10000 }, { x: 3000, y: 1500 });
|
|
||||||
movePlantTest("too low", { x: -10000, y: -10000 }, { x: 0, y: 0 });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("closePlantInfo()", () => {
|
|
||||||
it("no plant info open", () => {
|
|
||||||
mockPath = "/app/designer/plants";
|
|
||||||
const dispatch = jest.fn();
|
|
||||||
closePlantInfo(dispatch)();
|
|
||||||
expect(history.push).not.toHaveBeenCalled();
|
|
||||||
expect(dispatch).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("plant edit open", () => {
|
|
||||||
mockPath = "/app/designer/plants/1";
|
|
||||||
const dispatch = jest.fn();
|
|
||||||
closePlantInfo(dispatch)();
|
|
||||||
expect(history.push).toHaveBeenCalledWith("/app/designer/plants");
|
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
|
||||||
payload: undefined, type: Actions.SELECT_PLANT
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("plant info open", () => {
|
|
||||||
mockPath = "/app/designer/plants/1";
|
|
||||||
const dispatch = jest.fn();
|
|
||||||
closePlantInfo(dispatch)();
|
|
||||||
expect(history.push).toHaveBeenCalledWith("/app/designer/plants");
|
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
|
||||||
payload: undefined, type: Actions.SELECT_PLANT
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("setDragIcon()", () => {
|
|
||||||
it("sets the drag icon", () => {
|
|
||||||
const setDragImage = jest.fn();
|
|
||||||
const e = { currentTarget: new Image(), dataTransfer: { setDragImage } };
|
|
||||||
setDragIcon("icon")(e);
|
|
||||||
const img = new Image();
|
|
||||||
img.src = svgToUrl("icon");
|
|
||||||
expect(setDragImage).toHaveBeenCalledWith(img, 0, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets a default drag icon", () => {
|
|
||||||
const setDragImage = jest.fn();
|
|
||||||
const e = { currentTarget: new Image(), dataTransfer: { setDragImage } };
|
|
||||||
setDragIcon(undefined)(e);
|
|
||||||
const img = new Image();
|
|
||||||
img.src = DEFAULT_ICON;
|
|
||||||
expect(setDragImage).toHaveBeenCalledWith(img, 0, 0);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,54 +0,0 @@
|
||||||
jest.mock("../point_groups/group_detail", () => ({
|
|
||||||
fetchGroupFromUrl: jest.fn(() => mockGroup)
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("../../api/crud", () => ({
|
|
||||||
overwrite: jest.fn(),
|
|
||||||
edit: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mockMode = "none";
|
|
||||||
jest.mock("../map/util", () => ({ getMode: jest.fn(() => mockMode) }));
|
|
||||||
|
|
||||||
import {
|
|
||||||
fakePlant, fakePointGroup
|
|
||||||
} from "../../__test_support__/fake_state/resources";
|
|
||||||
import { fakeState } from "../../__test_support__/fake_state";
|
|
||||||
import { GetState } from "../../redux/interfaces";
|
|
||||||
import { clickMapPlant, selectPlant, toggleHoveredPlant } from "../actions";
|
|
||||||
import {
|
|
||||||
buildResourceIndex
|
|
||||||
} from "../../__test_support__/resource_index_builder";
|
|
||||||
import { overwrite } from "../../api/crud";
|
|
||||||
|
|
||||||
const mockGroup = fakePointGroup();
|
|
||||||
|
|
||||||
describe("clickMapPlant", () => {
|
|
||||||
// Base case
|
|
||||||
it("selects plants and toggles hovered plant", () => {
|
|
||||||
const state = fakeState();
|
|
||||||
const dispatch = jest.fn();
|
|
||||||
const getState: GetState = jest.fn(() => state);
|
|
||||||
clickMapPlant("foo", "bar")(dispatch, getState);
|
|
||||||
expect(dispatch).toHaveBeenCalledWith(selectPlant(["foo"]));
|
|
||||||
expect(dispatch).toHaveBeenCalledWith(toggleHoveredPlant("foo", "bar"));
|
|
||||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds a point to current group if group editor is active", () => {
|
|
||||||
mockMode = "addPointToGroup";
|
|
||||||
const state = fakeState();
|
|
||||||
const plant = fakePlant();
|
|
||||||
plant.body.id = 23;
|
|
||||||
state.resources = buildResourceIndex([plant]);
|
|
||||||
const dispatch = jest.fn();
|
|
||||||
const getState: GetState = jest.fn(() => state);
|
|
||||||
clickMapPlant(plant.uuid, "bar")(dispatch, getState);
|
|
||||||
expect(dispatch).toHaveBeenCalledWith(selectPlant([plant.uuid]));
|
|
||||||
expect(dispatch).toHaveBeenCalledWith(toggleHoveredPlant(plant.uuid, "bar"));
|
|
||||||
const xp =
|
|
||||||
expect.objectContaining({ name: "Fake", point_ids: [23] });
|
|
||||||
expect(overwrite).toHaveBeenCalledWith(mockGroup, xp);
|
|
||||||
expect(dispatch).toHaveBeenCalledTimes(3);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
let mockPath = "/app/designer/plants";
|
||||||
|
jest.mock("../../../history", () => ({
|
||||||
|
history: { push: jest.fn() },
|
||||||
|
getPathArray: jest.fn(() => { return mockPath.split("/"); })
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../api/crud", () => ({
|
||||||
|
edit: jest.fn(),
|
||||||
|
overwrite: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { fakePointGroup } from "../../../__test_support__/fake_state/resources";
|
||||||
|
const mockGroup = fakePointGroup();
|
||||||
|
jest.mock("../../point_groups/group_detail", () => ({
|
||||||
|
fetchGroupFromUrl: jest.fn(() => mockGroup)
|
||||||
|
}));
|
||||||
|
|
||||||
|
import {
|
||||||
|
movePlant, closePlantInfo, setDragIcon, clickMapPlant, selectPlant,
|
||||||
|
setHoveredPlant
|
||||||
|
} from "../actions";
|
||||||
|
import { MovePlantProps } from "../../interfaces";
|
||||||
|
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||||
|
import { edit, overwrite } from "../../../api/crud";
|
||||||
|
import { Actions } from "../../../constants";
|
||||||
|
import { DEFAULT_ICON, svgToUrl } from "../../../open_farm/icons";
|
||||||
|
import { history } from "../../../history";
|
||||||
|
import { fakeState } from "../../../__test_support__/fake_state";
|
||||||
|
import { GetState } from "../../../redux/interfaces";
|
||||||
|
import {
|
||||||
|
buildResourceIndex
|
||||||
|
} from "../../../__test_support__/resource_index_builder";
|
||||||
|
|
||||||
|
describe("movePlant", () => {
|
||||||
|
function movePlantTest(
|
||||||
|
caseDescription: string,
|
||||||
|
attempted: { x: number, y: number },
|
||||||
|
expected: { x: number, y: number }) {
|
||||||
|
it(`restricts plant to grid area: ${caseDescription}`, () => {
|
||||||
|
const payload: MovePlantProps = {
|
||||||
|
deltaX: attempted.x,
|
||||||
|
deltaY: attempted.y,
|
||||||
|
plant: fakePlant(),
|
||||||
|
gridSize: { x: 3000, y: 1500 }
|
||||||
|
};
|
||||||
|
movePlant(payload);
|
||||||
|
expect(edit).toHaveBeenCalledWith(
|
||||||
|
// Old plant
|
||||||
|
expect.objectContaining({
|
||||||
|
body: expect.objectContaining({
|
||||||
|
x: 100, y: 200
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
// Update
|
||||||
|
expect.objectContaining({
|
||||||
|
x: expected.x, y: expected.y
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
movePlantTest("within bounds", { x: 1, y: 2 }, { x: 101, y: 202 });
|
||||||
|
movePlantTest("too high", { x: 10000, y: 10000 }, { x: 3000, y: 1500 });
|
||||||
|
movePlantTest("too low", { x: -10000, y: -10000 }, { x: 0, y: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("closePlantInfo()", () => {
|
||||||
|
it("no plant info open", () => {
|
||||||
|
mockPath = "/app/designer/plants";
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
closePlantInfo(dispatch)();
|
||||||
|
expect(history.push).not.toHaveBeenCalled();
|
||||||
|
expect(dispatch).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("plant edit open", () => {
|
||||||
|
mockPath = "/app/designer/plants/1";
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
closePlantInfo(dispatch)();
|
||||||
|
expect(history.push).toHaveBeenCalledWith("/app/designer/plants");
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: undefined, type: Actions.SELECT_PLANT
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("plant info open", () => {
|
||||||
|
mockPath = "/app/designer/plants/1";
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
closePlantInfo(dispatch)();
|
||||||
|
expect(history.push).toHaveBeenCalledWith("/app/designer/plants");
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: undefined, type: Actions.SELECT_PLANT
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("setDragIcon()", () => {
|
||||||
|
it("sets the drag icon", () => {
|
||||||
|
const setDragImage = jest.fn();
|
||||||
|
const e = { currentTarget: new Image(), dataTransfer: { setDragImage } };
|
||||||
|
setDragIcon("icon")(e);
|
||||||
|
const img = new Image();
|
||||||
|
img.src = svgToUrl("icon");
|
||||||
|
expect(setDragImage).toHaveBeenCalledWith(img, 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets a default drag icon", () => {
|
||||||
|
const setDragImage = jest.fn();
|
||||||
|
const e = { currentTarget: new Image(), dataTransfer: { setDragImage } };
|
||||||
|
setDragIcon(undefined)(e);
|
||||||
|
const img = new Image();
|
||||||
|
img.src = DEFAULT_ICON;
|
||||||
|
expect(setDragImage).toHaveBeenCalledWith(img, 0, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("clickMapPlant", () => {
|
||||||
|
it("selects plants and toggles hovered plant", () => {
|
||||||
|
const state = fakeState();
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
const getState: GetState = jest.fn(() => state);
|
||||||
|
clickMapPlant("foo", "bar")(dispatch, getState);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith(selectPlant(["foo"]));
|
||||||
|
expect(dispatch).toHaveBeenCalledWith(setHoveredPlant("foo", "bar"));
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a point to current group if group editor is active", () => {
|
||||||
|
mockPath = "/app/designer/groups/1";
|
||||||
|
mockGroup.body.point_ids = [1];
|
||||||
|
const state = fakeState();
|
||||||
|
const plant = fakePlant();
|
||||||
|
plant.body.id = 23;
|
||||||
|
state.resources = buildResourceIndex([plant]);
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
const getState: GetState = jest.fn(() => state);
|
||||||
|
clickMapPlant(plant.uuid, "bar")(dispatch, getState);
|
||||||
|
expect(overwrite).toHaveBeenCalledWith(mockGroup, expect.objectContaining({
|
||||||
|
name: "Fake", point_ids: [1, 23]
|
||||||
|
}));
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes a point from the current group if group editor is active", () => {
|
||||||
|
mockPath = "/app/designer/groups/1";
|
||||||
|
mockGroup.body.point_ids = [1, 2];
|
||||||
|
const state = fakeState();
|
||||||
|
const plant = fakePlant();
|
||||||
|
plant.body.id = 2;
|
||||||
|
state.resources = buildResourceIndex([plant]);
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
const getState: GetState = jest.fn(() => state);
|
||||||
|
clickMapPlant(plant.uuid, "bar")(dispatch, getState);
|
||||||
|
expect(overwrite).toHaveBeenCalledWith(mockGroup, expect.objectContaining({
|
||||||
|
name: "Fake", point_ids: [1]
|
||||||
|
}));
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
jest.mock("../../actions", () => ({
|
jest.mock("../actions", () => ({
|
||||||
unselectPlant: jest.fn(() => jest.fn()),
|
unselectPlant: jest.fn(() => jest.fn()),
|
||||||
closePlantInfo: jest.fn(() => jest.fn()),
|
closePlantInfo: jest.fn(() => jest.fn()),
|
||||||
}));
|
}));
|
||||||
|
@ -41,7 +41,7 @@ import { GardenMap } from "../garden_map";
|
||||||
import { shallow, mount } from "enzyme";
|
import { shallow, mount } from "enzyme";
|
||||||
import { GardenMapProps } from "../../interfaces";
|
import { GardenMapProps } from "../../interfaces";
|
||||||
import { setEggStatus, EggKeys } from "../easter_eggs/status";
|
import { setEggStatus, EggKeys } from "../easter_eggs/status";
|
||||||
import { unselectPlant, closePlantInfo } from "../../actions";
|
import { unselectPlant, closePlantInfo } from "../actions";
|
||||||
import {
|
import {
|
||||||
dropPlant, beginPlantDrag, maybeSavePlantLocation, dragPlant
|
dropPlant, beginPlantDrag, maybeSavePlantLocation, dragPlant
|
||||||
} from "../layers/plants/plant_actions";
|
} from "../layers/plants/plant_actions";
|
||||||
|
|
|
@ -348,7 +348,7 @@ describe("getMode()", () => {
|
||||||
mockGardenOpen = true;
|
mockGardenOpen = true;
|
||||||
expect(getMode()).toEqual(Mode.templateView);
|
expect(getMode()).toEqual(Mode.templateView);
|
||||||
mockPath = "/app/designer/groups/1";
|
mockPath = "/app/designer/groups/1";
|
||||||
expect(getMode()).toEqual(Mode.addPointToGroup);
|
expect(getMode()).toEqual(Mode.editGroup);
|
||||||
mockPath = "";
|
mockPath = "";
|
||||||
mockGardenOpen = false;
|
mockGardenOpen = false;
|
||||||
expect(getMode()).toEqual(Mode.none);
|
expect(getMode()).toEqual(Mode.none);
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { MovePlantProps, DraggableEvent } from "./interfaces";
|
import { MovePlantProps, DraggableEvent } from "../interfaces";
|
||||||
import { defensiveClone } from "../util";
|
import { defensiveClone } from "../../util";
|
||||||
import { edit, overwrite } from "../api/crud";
|
import { edit, overwrite } from "../../api/crud";
|
||||||
import { history } from "../history";
|
import { history } from "../../history";
|
||||||
import { Actions } from "../constants";
|
import { Actions } from "../../constants";
|
||||||
import { svgToUrl, DEFAULT_ICON } from "../open_farm/icons";
|
import { svgToUrl, DEFAULT_ICON } from "../../open_farm/icons";
|
||||||
import { Mode } from "./map/interfaces";
|
import { Mode } from "../map/interfaces";
|
||||||
import { clamp, uniq } from "lodash";
|
import { clamp, uniq } from "lodash";
|
||||||
import { GetState } from "../redux/interfaces";
|
import { GetState } from "../../redux/interfaces";
|
||||||
import { fetchGroupFromUrl } from "./point_groups/group_detail";
|
import { fetchGroupFromUrl } from "../point_groups/group_detail";
|
||||||
import { TaggedPoint } from "farmbot";
|
import { TaggedPoint } from "farmbot";
|
||||||
import { getMode } from "./map/util";
|
import { getMode } from "../map/util";
|
||||||
|
|
||||||
export function movePlant(payload: MovePlantProps) {
|
export function movePlant(payload: MovePlantProps) {
|
||||||
const tr = payload.plant;
|
const tr = payload.plant;
|
||||||
|
@ -25,8 +25,8 @@ export const selectPlant = (payload: string[] | undefined) => {
|
||||||
return { type: Actions.SELECT_PLANT, payload };
|
return { type: Actions.SELECT_PLANT, payload };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toggleHoveredPlant =
|
export const setHoveredPlant =
|
||||||
(plantUUID: string | undefined, icon: string) => {
|
(plantUUID: string | undefined, icon = "") => {
|
||||||
return {
|
return {
|
||||||
type: Actions.TOGGLE_HOVERED_PLANT,
|
type: Actions.TOGGLE_HOVERED_PLANT,
|
||||||
payload: { plantUUID, icon }
|
payload: { plantUUID, icon }
|
||||||
|
@ -35,29 +35,33 @@ export const toggleHoveredPlant =
|
||||||
|
|
||||||
export const clickMapPlant = (clickedPlantUuid: string, icon: string) => {
|
export const clickMapPlant = (clickedPlantUuid: string, icon: string) => {
|
||||||
return (dispatch: Function, getState: GetState) => {
|
return (dispatch: Function, getState: GetState) => {
|
||||||
dispatch(selectPlant([clickedPlantUuid]));
|
if (getMode() === Mode.editGroup) {
|
||||||
dispatch(toggleHoveredPlant(clickedPlantUuid, icon));
|
|
||||||
const isEditingGroup = getMode() === Mode.addPointToGroup;
|
|
||||||
if (isEditingGroup) {
|
|
||||||
const { resources } = getState();
|
const { resources } = getState();
|
||||||
const group = fetchGroupFromUrl(resources.index);
|
const group = fetchGroupFromUrl(resources.index);
|
||||||
const point =
|
const point =
|
||||||
resources.index.references[clickedPlantUuid] as TaggedPoint | undefined;
|
resources.index.references[clickedPlantUuid] as TaggedPoint | undefined;
|
||||||
if (group && point && point.body.id) {
|
if (group && point && point.body.id) {
|
||||||
type Body = (typeof group)["body"];
|
type Body = (typeof group)["body"];
|
||||||
const nextGroup: Body =
|
const nextGroup: Body = ({
|
||||||
({ ...group.body, point_ids: [...group.body.point_ids] });
|
...group.body,
|
||||||
nextGroup.point_ids.push(point.body.id);
|
point_ids: [...group.body.point_ids.filter(p => p != point.body.id)]
|
||||||
|
});
|
||||||
|
if (!group.body.point_ids.includes(point.body.id)) {
|
||||||
|
nextGroup.point_ids.push(point.body.id);
|
||||||
|
}
|
||||||
nextGroup.point_ids = uniq(nextGroup.point_ids);
|
nextGroup.point_ids = uniq(nextGroup.point_ids);
|
||||||
dispatch(overwrite(group, nextGroup));
|
dispatch(overwrite(group, nextGroup));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
dispatch(selectPlant([clickedPlantUuid]));
|
||||||
|
dispatch(setHoveredPlant(clickedPlantUuid, icon));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const unselectPlant = (dispatch: Function) => () => {
|
export const unselectPlant = (dispatch: Function) => () => {
|
||||||
dispatch(selectPlant(undefined));
|
dispatch(selectPlant(undefined));
|
||||||
dispatch(toggleHoveredPlant(undefined, ""));
|
dispatch(setHoveredPlant(undefined));
|
||||||
dispatch({ type: Actions.HOVER_PLANT_LIST_ITEM, payload: undefined });
|
dispatch({ type: Actions.HOVER_PLANT_LIST_ITEM, payload: undefined });
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { TaggedPlant, AxisNumberProperty, Mode } from "../interfaces";
|
||||||
import { SelectionBoxData } from "./selection_box";
|
import { SelectionBoxData } from "./selection_box";
|
||||||
import { GardenMapState } from "../../interfaces";
|
import { GardenMapState } from "../../interfaces";
|
||||||
import { history } from "../../../history";
|
import { history } from "../../../history";
|
||||||
import { selectPlant } from "../../actions";
|
import { selectPlant } from "../actions";
|
||||||
import { getMode } from "../util";
|
import { getMode } from "../util";
|
||||||
|
|
||||||
/** Return all plants within the selection box. */
|
/** Return all plants within the selection box. */
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { BooleanSetting } from "../../session_keys";
|
import { BooleanSetting } from "../../session_keys";
|
||||||
import { closePlantInfo, unselectPlant } from "../actions";
|
import { closePlantInfo, unselectPlant } from "./actions";
|
||||||
import {
|
import {
|
||||||
MapTransformProps, TaggedPlant, Mode, AxisNumberProperty
|
MapTransformProps, TaggedPlant, Mode, AxisNumberProperty
|
||||||
} from "./interfaces";
|
} from "./interfaces";
|
||||||
|
|
|
@ -146,5 +146,5 @@ export enum Mode {
|
||||||
points = "points",
|
points = "points",
|
||||||
createPoint = "createPoint",
|
createPoint = "createPoint",
|
||||||
templateView = "templateView",
|
templateView = "templateView",
|
||||||
addPointToGroup = "addPointToGroup",
|
editGroup = "editGroup",
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ jest.mock("../../../../../open_farm/cached_crop", () => ({
|
||||||
cachedCrop: jest.fn(p => Promise.resolve({ spread: mockSpreads[p] })),
|
cachedCrop: jest.fn(p => Promise.resolve({ spread: mockSpreads[p] })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../../../actions", () => ({
|
jest.mock("../../../actions", () => ({
|
||||||
movePlant: jest.fn(),
|
movePlant: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ import { cachedCrop } from "../../../../../open_farm/cached_crop";
|
||||||
import {
|
import {
|
||||||
fakeMapTransformProps
|
fakeMapTransformProps
|
||||||
} from "../../../../../__test_support__/map_transform_props";
|
} from "../../../../../__test_support__/map_transform_props";
|
||||||
import { movePlant } from "../../../../actions";
|
import { movePlant } from "../../../actions";
|
||||||
import {
|
import {
|
||||||
fakeCropLiveSearchResult
|
fakeCropLiveSearchResult
|
||||||
} from "../../../../../__test_support__/fake_crop_search_result";
|
} from "../../../../../__test_support__/fake_crop_search_result";
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { DragHelpers } from "../../active_plant/drag_helpers";
|
||||||
import { Color } from "../../../../ui/index";
|
import { Color } from "../../../../ui/index";
|
||||||
import { Actions } from "../../../../constants";
|
import { Actions } from "../../../../constants";
|
||||||
import { cachedCrop } from "../../../../open_farm/cached_crop";
|
import { cachedCrop } from "../../../../open_farm/cached_crop";
|
||||||
import { clickMapPlant } from "../../../actions";
|
import { clickMapPlant } from "../../actions";
|
||||||
import { Circle } from "./circle";
|
import { Circle } from "./circle";
|
||||||
|
|
||||||
export class GardenPlant extends
|
export class GardenPlant extends
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { CropLiveSearchResult, GardenMapState } from "../../../interfaces";
|
||||||
import { getPathArray } from "../../../../history";
|
import { getPathArray } from "../../../../history";
|
||||||
import { findBySlug } from "../../../search_selectors";
|
import { findBySlug } from "../../../search_selectors";
|
||||||
import { transformXY, round, getZoomLevelFromMap } from "../../util";
|
import { transformXY, round, getZoomLevelFromMap } from "../../util";
|
||||||
import { movePlant } from "../../../actions";
|
import { movePlant } from "../../actions";
|
||||||
import { cachedCrop } from "../../../../open_farm/cached_crop";
|
import { cachedCrop } from "../../../../open_farm/cached_crop";
|
||||||
import { t } from "../../../../i18next_wrapper";
|
import { t } from "../../../../i18next_wrapper";
|
||||||
import { error } from "../../../../toast/toast";
|
import { error } from "../../../../toast/toast";
|
||||||
|
|
|
@ -44,7 +44,7 @@ export function PlantLayer(props: PlantLayerProps) {
|
||||||
style: maybeNoPointer(p.body.id ? {} : { pointerEvents: "none" }),
|
style: maybeNoPointer(p.body.id ? {} : { pointerEvents: "none" }),
|
||||||
key: p.uuid,
|
key: p.uuid,
|
||||||
};
|
};
|
||||||
return getMode() === Mode.addPointToGroup
|
return getMode() === Mode.editGroup
|
||||||
? <g {...wrapperProps}>{plant}</g>
|
? <g {...wrapperProps}>{plant}</g>
|
||||||
: <Link {...wrapperProps}
|
: <Link {...wrapperProps}
|
||||||
to={`/app/designer/${plantCategory}/${"" + p.body.id}`}>
|
to={`/app/designer/${plantCategory}/${"" + p.body.id}`}>
|
||||||
|
|
|
@ -291,9 +291,7 @@ export const transformForQuadrant =
|
||||||
export const getMode = (): Mode => {
|
export const getMode = (): Mode => {
|
||||||
const pathArray = getPathArray();
|
const pathArray = getPathArray();
|
||||||
if (pathArray) {
|
if (pathArray) {
|
||||||
if ((pathArray[3] === "groups") && pathArray[4]) {
|
if ((pathArray[3] === "groups") && pathArray[4]) { return Mode.editGroup; }
|
||||||
return Mode.addPointToGroup;
|
|
||||||
}
|
|
||||||
if (pathArray[6] === "add") { return Mode.clickToAdd; }
|
if (pathArray[6] === "add") { return Mode.clickToAdd; }
|
||||||
if (!isNaN(parseInt(pathArray.slice(-1)[0]))) { return Mode.editPlant; }
|
if (!isNaN(parseInt(pathArray.slice(-1)[0]))) { return Mode.editPlant; }
|
||||||
if (pathArray[5] === "edit") { return Mode.editPlant; }
|
if (pathArray[5] === "edit") { return Mode.editPlant; }
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { AxisInputBox } from "../controls/axis_input_box";
|
||||||
import { isNumber } from "lodash";
|
import { isNumber } from "lodash";
|
||||||
import { Actions, Content } from "../constants";
|
import { Actions, Content } from "../constants";
|
||||||
import { validBotLocationData } from "../util/util";
|
import { validBotLocationData } from "../util/util";
|
||||||
import { unselectPlant } from "./actions";
|
import { unselectPlant } from "./map/actions";
|
||||||
import { AxisNumberProperty } from "./map/interfaces";
|
import { AxisNumberProperty } from "./map/interfaces";
|
||||||
import {
|
import {
|
||||||
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
|
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
|
||||||
|
|
|
@ -6,7 +6,7 @@ jest.mock("../../../history", () => ({
|
||||||
|
|
||||||
jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));
|
jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));
|
||||||
|
|
||||||
jest.mock("../../actions", () => ({
|
jest.mock("../../map/actions", () => ({
|
||||||
unselectPlant: jest.fn(() => jest.fn()),
|
unselectPlant: jest.fn(() => jest.fn()),
|
||||||
setDragIcon: jest.fn(),
|
setDragIcon: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
@ -20,7 +20,7 @@ import { history } from "../../../history";
|
||||||
import {
|
import {
|
||||||
fakeCropLiveSearchResult
|
fakeCropLiveSearchResult
|
||||||
} from "../../../__test_support__/fake_crop_search_result";
|
} from "../../../__test_support__/fake_crop_search_result";
|
||||||
import { unselectPlant } from "../../actions";
|
import { unselectPlant } from "../../map/actions";
|
||||||
import { svgToUrl } from "../../../open_farm/icons";
|
import { svgToUrl } from "../../../open_farm/icons";
|
||||||
|
|
||||||
describe("<CropInfo />", () => {
|
describe("<CropInfo />", () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Everything } from "../../interfaces";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { svgToUrl } from "../../open_farm/icons";
|
import { svgToUrl } from "../../open_farm/icons";
|
||||||
import { CropLiveSearchResult, OpenfarmSearch } from "../interfaces";
|
import { CropLiveSearchResult, OpenfarmSearch } from "../interfaces";
|
||||||
import { setDragIcon } from "../actions";
|
import { setDragIcon } from "../map/actions";
|
||||||
import { getCropHeaderProps, searchForCurrentCrop } from "./crop_info";
|
import { getCropHeaderProps, searchForCurrentCrop } from "./crop_info";
|
||||||
import { DesignerPanel, DesignerPanelHeader } from "./designer_panel";
|
import { DesignerPanel, DesignerPanelHeader } from "./designer_panel";
|
||||||
import { OFSearch } from "../util";
|
import { OFSearch } from "../util";
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { findBySlug } from "../search_selectors";
|
||||||
import { Everything } from "../../interfaces";
|
import { Everything } from "../../interfaces";
|
||||||
import { OpenFarm } from "../openfarm";
|
import { OpenFarm } from "../openfarm";
|
||||||
import { OFSearch } from "../util";
|
import { OFSearch } from "../util";
|
||||||
import { unselectPlant, setDragIcon } from "../actions";
|
import { unselectPlant, setDragIcon } from "../map/actions";
|
||||||
import { validBotLocationData } from "../../util";
|
import { validBotLocationData } from "../../util";
|
||||||
import { createPlant } from "../map/layers/plants/plant_actions";
|
import { createPlant } from "../map/layers/plants/plant_actions";
|
||||||
import { round } from "../map/util";
|
import { round } from "../map/util";
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { mapStateToProps, formatPlantInfo } from "./map_state_to_props";
|
import { mapStateToProps, formatPlantInfo } from "./map_state_to_props";
|
||||||
import { PlantPanel } from "./plant_panel";
|
import { PlantPanel } from "./plant_panel";
|
||||||
import { unselectPlant } from "../actions";
|
import { unselectPlant } from "../map/actions";
|
||||||
import { TaggedPlant } from "../map/interfaces";
|
import { TaggedPlant } from "../map/interfaces";
|
||||||
import { DesignerPanel, DesignerPanelHeader } from "./designer_panel";
|
import { DesignerPanel, DesignerPanelHeader } from "./designer_panel";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { get } from "lodash";
|
||||||
import { unpackUUID } from "../../util";
|
import { unpackUUID } from "../../util";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { cachedCrop } from "../../open_farm/cached_crop";
|
import { cachedCrop } from "../../open_farm/cached_crop";
|
||||||
import { selectPlant, toggleHoveredPlant } from "../actions";
|
import { selectPlant, setHoveredPlant } from "../map/actions";
|
||||||
|
|
||||||
type IMGEvent = React.SyntheticEvent<HTMLImageElement>;
|
type IMGEvent = React.SyntheticEvent<HTMLImageElement>;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export class PlantInventoryItem extends
|
||||||
const isEnter = action === "enter";
|
const isEnter = action === "enter";
|
||||||
const plantUUID = isEnter ? tpp.uuid : undefined;
|
const plantUUID = isEnter ? tpp.uuid : undefined;
|
||||||
const icon = isEnter ? this.state.icon : "";
|
const icon = isEnter ? this.state.icon : "";
|
||||||
dispatch(toggleHoveredPlant(plantUUID, icon));
|
dispatch(setHoveredPlant(plantUUID, icon));
|
||||||
};
|
};
|
||||||
|
|
||||||
const click = () => {
|
const click = () => {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { connect } from "react-redux";
|
||||||
import { Everything } from "../../interfaces";
|
import { Everything } from "../../interfaces";
|
||||||
import { PlantInventoryItem } from "./plant_inventory_item";
|
import { PlantInventoryItem } from "./plant_inventory_item";
|
||||||
import { destroy } from "../../api/crud";
|
import { destroy } from "../../api/crud";
|
||||||
import { unselectPlant, selectPlant, toggleHoveredPlant } from "../actions";
|
import { unselectPlant, selectPlant, setHoveredPlant } from "../map/actions";
|
||||||
import { Actions, Content } from "../../constants";
|
import { Actions, Content } from "../../constants";
|
||||||
import { TaggedPlant } from "../map/interfaces";
|
import { TaggedPlant } from "../map/interfaces";
|
||||||
import { getPlants } from "../state_to_props";
|
import { getPlants } from "../state_to_props";
|
||||||
|
@ -36,7 +36,7 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
||||||
if (selected && selected.length == 1) {
|
if (selected && selected.length == 1) {
|
||||||
unselectPlant(dispatch)();
|
unselectPlant(dispatch)();
|
||||||
} else {
|
} else {
|
||||||
dispatch(toggleHoveredPlant(undefined, ""));
|
dispatch(setHoveredPlant(undefined));
|
||||||
dispatch({ type: Actions.HOVER_PLANT_LIST_ITEM, payload: undefined });
|
dispatch({ type: Actions.HOVER_PLANT_LIST_ITEM, payload: undefined });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,7 @@ jest.mock("../../../api/crud", () => ({
|
||||||
edit: jest.fn(),
|
edit: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../actions", () => ({
|
jest.mock("../../map/actions", () => ({ setHoveredPlant: jest.fn() }));
|
||||||
toggleHoveredPlant: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mockDev = false;
|
let mockDev = false;
|
||||||
jest.mock("../../../account/dev/dev_support", () => ({
|
jest.mock("../../../account/dev/dev_support", () => ({
|
||||||
|
|
|
@ -71,7 +71,7 @@ describe("nearest neighbor algorithm", () => {
|
||||||
const p4 = fakePlant();
|
const p4 = fakePlant();
|
||||||
p4.body.x = 1000;
|
p4.body.x = 1000;
|
||||||
p4.body.y = 150;
|
p4.body.y = 150;
|
||||||
const points = nn([p4, p2, p3, p1]);
|
const points = nn([p4, p2, p3, p1, p1]);
|
||||||
expect(points).toEqual([p1, p2, p3, p4]);
|
expect(points).toEqual([p1, p2, p3, p4]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
jest.mock("../../actions", () => ({ toggleHoveredPlant: jest.fn() }));
|
jest.mock("../../map/actions", () => ({ setHoveredPlant: jest.fn() }));
|
||||||
jest.mock("../../../api/crud", () => ({ overwrite: jest.fn() }));
|
jest.mock("../../../api/crud", () => ({ overwrite: jest.fn() }));
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { PointGroupItem } from "../point_group_item";
|
import { PointGroupItem } from "../point_group_item";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import { fakePlant, fakePointGroup } from "../../../__test_support__/fake_state/resources";
|
import {
|
||||||
|
fakePlant, fakePointGroup
|
||||||
|
} from "../../../__test_support__/fake_state/resources";
|
||||||
import { DeepPartial } from "redux";
|
import { DeepPartial } from "redux";
|
||||||
import { cachedCrop } from "../../../open_farm/cached_crop";
|
import { cachedCrop } from "../../../open_farm/cached_crop";
|
||||||
import { toggleHoveredPlant } from "../../actions";
|
import { setHoveredPlant } from "../../map/actions";
|
||||||
import { overwrite } from "../../../api/crud";
|
import { overwrite } from "../../../api/crud";
|
||||||
|
|
||||||
describe("<PointGroupItem/>", () => {
|
describe("<PointGroupItem/>", () => {
|
||||||
|
@ -51,8 +53,7 @@ describe("<PointGroupItem/>", () => {
|
||||||
i.state.icon = "X";
|
i.state.icon = "X";
|
||||||
i.enter();
|
i.enter();
|
||||||
expect(i.props.dispatch).toHaveBeenCalledTimes(1);
|
expect(i.props.dispatch).toHaveBeenCalledTimes(1);
|
||||||
expect(toggleHoveredPlant)
|
expect(setHoveredPlant).toHaveBeenCalledWith(i.props.plant.uuid, "X");
|
||||||
.toHaveBeenCalledWith(i.props.plant.uuid, "X");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles mouse exit", () => {
|
it("handles mouse exit", () => {
|
||||||
|
@ -60,13 +61,13 @@ describe("<PointGroupItem/>", () => {
|
||||||
i.state.icon = "X";
|
i.state.icon = "X";
|
||||||
i.leave();
|
i.leave();
|
||||||
expect(i.props.dispatch).toHaveBeenCalledTimes(1);
|
expect(i.props.dispatch).toHaveBeenCalledTimes(1);
|
||||||
expect(toggleHoveredPlant).toHaveBeenCalledWith(undefined, "");
|
expect(setHoveredPlant).toHaveBeenCalledWith(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles clicks", () => {
|
it("handles clicks", () => {
|
||||||
const i = new PointGroupItem(newProps());
|
const i = new PointGroupItem(newProps());
|
||||||
i.click();
|
i.click();
|
||||||
expect(i.props.dispatch).toHaveBeenCalledTimes(1);
|
expect(i.props.dispatch).toHaveBeenCalledTimes(2);
|
||||||
expect(overwrite).toHaveBeenCalledWith({
|
expect(overwrite).toHaveBeenCalledWith({
|
||||||
body: { name: "Fake", point_ids: [], sort_type: "xy_ascending" },
|
body: { name: "Fake", point_ids: [], sort_type: "xy_ascending" },
|
||||||
kind: "PointGroup",
|
kind: "PointGroup",
|
||||||
|
@ -77,5 +78,6 @@ describe("<PointGroupItem/>", () => {
|
||||||
point_ids: [],
|
point_ids: [],
|
||||||
sort_type: "xy_ascending",
|
sort_type: "xy_ascending",
|
||||||
});
|
});
|
||||||
|
expect(setHoveredPlant).toHaveBeenCalledWith(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
DesignerPanelHeader
|
DesignerPanelHeader
|
||||||
} from "../plants/designer_panel";
|
} from "../plants/designer_panel";
|
||||||
import { TaggedPointGroup } from "farmbot";
|
import { TaggedPointGroup } from "farmbot";
|
||||||
import { DeleteButton } from "../../controls/pin_form_fields";
|
import { DeleteButton } from "../../ui/delete_button";
|
||||||
import { save, edit } from "../../api/crud";
|
import { save, edit } from "../../api/crud";
|
||||||
import { TaggedPlant } from "../map/interfaces";
|
import { TaggedPlant } from "../map/interfaces";
|
||||||
import { PointGroupSortSelector, sortGroupBy } from "./point_group_sort_selector";
|
import { PointGroupSortSelector, sortGroupBy } from "./point_group_sort_selector";
|
||||||
|
|
|
@ -40,6 +40,7 @@ export const nn = (points: TaggedPlant[]) => {
|
||||||
const ordered: TaggedPlant[] = [];
|
const ordered: TaggedPlant[] = [];
|
||||||
let from = { x: 0, y: 0 };
|
let from = { x: 0, y: 0 };
|
||||||
points.map(() => {
|
points.map(() => {
|
||||||
|
if (available.length < 1) { return; }
|
||||||
const nearest = findNearest(from, available);
|
const nearest = findNearest(from, available);
|
||||||
ordered.push(nearest);
|
ordered.push(nearest);
|
||||||
from = { x: nearest.body.x, y: nearest.body.y };
|
from = { x: nearest.body.x, y: nearest.body.y };
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||||
import { DEFAULT_ICON, svgToUrl } from "../../open_farm/icons";
|
import { DEFAULT_ICON, svgToUrl } from "../../open_farm/icons";
|
||||||
import { TaggedPlant } from "../map/interfaces";
|
import { TaggedPlant } from "../map/interfaces";
|
||||||
import { cachedCrop } from "../../open_farm/cached_crop";
|
import { cachedCrop } from "../../open_farm/cached_crop";
|
||||||
import { toggleHoveredPlant } from "../actions";
|
import { setHoveredPlant } from "../map/actions";
|
||||||
import { TaggedPointGroup, uuid } from "farmbot";
|
import { TaggedPointGroup, uuid } from "farmbot";
|
||||||
import { overwrite } from "../../api/crud";
|
import { overwrite } from "../../api/crud";
|
||||||
|
|
||||||
|
@ -25,23 +25,23 @@ const removePoint = (group: TaggedPointGroup, pointId: number) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// The individual plants in the point group detail page.
|
// The individual plants in the point group detail page.
|
||||||
export class PointGroupItem extends React.Component<PointGroupItemProps, PointGroupItemState> {
|
export class PointGroupItem
|
||||||
|
extends React.Component<PointGroupItemProps, PointGroupItemState> {
|
||||||
|
|
||||||
state: PointGroupItemState = { icon: "" };
|
state: PointGroupItemState = { icon: "" };
|
||||||
|
|
||||||
key = uuid();
|
key = uuid();
|
||||||
|
|
||||||
enter = () => this
|
enter = () => this.props.dispatch(
|
||||||
.props
|
setHoveredPlant(this.props.plant.uuid, this.state.icon));
|
||||||
.dispatch(toggleHoveredPlant(this.props.plant.uuid, this.state.icon));
|
|
||||||
|
|
||||||
leave = () => this
|
leave = () => this.props.dispatch(setHoveredPlant(undefined));
|
||||||
.props
|
|
||||||
.dispatch(toggleHoveredPlant(undefined, ""));
|
|
||||||
|
|
||||||
click = () => this
|
click = () => {
|
||||||
.props
|
this.props.dispatch(
|
||||||
.dispatch(removePoint(this.props.group, this.props.plant.body.id || 0));
|
removePoint(this.props.group, this.props.plant.body.id || 0));
|
||||||
|
this.leave();
|
||||||
|
}
|
||||||
|
|
||||||
maybeGetCachedIcon = ({ currentTarget }: IMGEvent) => {
|
maybeGetCachedIcon = ({ currentTarget }: IMGEvent) => {
|
||||||
return cachedCrop(this.props.plant.body.openfarm_slug).then((crop) => {
|
return cachedCrop(this.props.plant.body.openfarm_slug).then((crop) => {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||||
import { Everything } from "../../interfaces";
|
import { Everything } from "../../interfaces";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { history } from "../../history";
|
import { history } from "../../history";
|
||||||
import { unselectPlant } from "../actions";
|
import { unselectPlant } from "../map/actions";
|
||||||
import {
|
import {
|
||||||
selectAllSavedGardens, selectAllPlantTemplates, selectAllPlantPointers
|
selectAllSavedGardens, selectAllPlantTemplates, selectAllPlantPointers
|
||||||
} from "../../resources/selectors";
|
} from "../../resources/selectors";
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
jest.mock("../../i18n", () => {
|
jest.mock("../../i18n", () => ({
|
||||||
return { detectLanguage: jest.fn(() => Promise.resolve()) };
|
detectLanguage: jest.fn(() => Promise.resolve())
|
||||||
});
|
}));
|
||||||
|
|
||||||
jest.mock("../../util/stop_ie", () => {
|
jest.mock("../../util/stop_ie", () => ({ stopIE: jest.fn() }));
|
||||||
return { stopIE: jest.fn() };
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock("../../util",
|
jest.mock("../../util", () => ({
|
||||||
() => ({ attachToRoot: jest.fn(), trim: (s: string) => s }));
|
attachToRoot: jest.fn(),
|
||||||
|
trim: (s: string) => s,
|
||||||
|
}));
|
||||||
|
|
||||||
import { detectLanguage } from "../../i18n";
|
import { detectLanguage } from "../../i18n";
|
||||||
import { stopIE } from "../../util/stop_ie";
|
import { stopIE } from "../../util/stop_ie";
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import { findIndex } from "lodash";
|
import { findIndex } from "lodash";
|
||||||
import { t } from "./i18next_wrapper";
|
import { t } from "./i18next_wrapper";
|
||||||
import { unselectPlant } from "./farm_designer/actions";
|
import { unselectPlant } from "./farm_designer/map/actions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import { DeleteButton } from "../delete_button";
|
||||||
|
|
||||||
|
describe("<DeleteButton />", () => {
|
||||||
|
const fakeProps = () => ({
|
||||||
|
dispatch: jest.fn(() => Promise.resolve()),
|
||||||
|
uuid: "resource uuid",
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deletes resource", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow(<DeleteButton {...p} />);
|
||||||
|
wrapper.find("button").simulate("click");
|
||||||
|
expect(p.dispatch).toHaveBeenCalledWith(expect.any(Function));
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { destroy } from "../api/crud";
|
||||||
|
import { t } from "../i18next_wrapper";
|
||||||
|
import { UUID } from "../resources/interfaces";
|
||||||
|
import { omit } from "lodash";
|
||||||
|
|
||||||
|
interface ButtonCustomProps {
|
||||||
|
dispatch: Function;
|
||||||
|
uuid: UUID;
|
||||||
|
children?: React.ReactChild
|
||||||
|
onDestroy?: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ButtonHtmlProps =
|
||||||
|
React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||||
|
|
||||||
|
type DeleteButtonProps =
|
||||||
|
ButtonCustomProps & ButtonHtmlProps;
|
||||||
|
|
||||||
|
/** Unfortunately, React will trigger a runtime
|
||||||
|
* warning if we pass extra props to HTML elements */
|
||||||
|
const OMIT_THESE: Record<keyof ButtonCustomProps, true> = {
|
||||||
|
"dispatch": true,
|
||||||
|
"uuid": true,
|
||||||
|
"children": true,
|
||||||
|
"onDestroy": true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DeleteButton = (props: DeleteButtonProps) =>
|
||||||
|
<button
|
||||||
|
{...omit(props, Object.keys(OMIT_THESE))}
|
||||||
|
className="red fb-button del-button"
|
||||||
|
title={t("Delete")}
|
||||||
|
onClick={() =>
|
||||||
|
props.dispatch(destroy(props.uuid))
|
||||||
|
.then(props.onDestroy || (() => { }))}>
|
||||||
|
{props.children || <i className="fa fa-times" />}
|
||||||
|
</button>;
|
|
@ -1,11 +1,22 @@
|
||||||
export * from "./back_arrow";
|
export * from "./back_arrow";
|
||||||
export * from "./blurable_input";
|
export * from "./blurable_input";
|
||||||
export * from "./colors";
|
export * from "./center_panel";
|
||||||
export * from "./color_picker";
|
export * from "./color_picker";
|
||||||
|
export * from "./colors";
|
||||||
export * from "./column";
|
export * from "./column";
|
||||||
|
// export * from "./delete_button";
|
||||||
|
export * from "./doc_link";
|
||||||
|
export * from "./empty_state_wrapper";
|
||||||
|
export * from "./expandable_header";
|
||||||
|
export * from "./fallback_img";
|
||||||
|
export * from "./fb_select";
|
||||||
export * from "./help";
|
export * from "./help";
|
||||||
export * from "./page";
|
export * from "./input_error";
|
||||||
|
export * from "./left_panel";
|
||||||
export * from "./markdown";
|
export * from "./markdown";
|
||||||
|
export * from "./new_fb_select";
|
||||||
|
export * from "./page";
|
||||||
|
export * from "./right_panel";
|
||||||
export * from "./row";
|
export * from "./row";
|
||||||
export * from "./saucer";
|
export * from "./saucer";
|
||||||
export * from "./save_button";
|
export * from "./save_button";
|
||||||
|
@ -14,10 +25,3 @@ export * from "./widget";
|
||||||
export * from "./widget_header";
|
export * from "./widget_header";
|
||||||
export * from "./widget_footer";
|
export * from "./widget_footer";
|
||||||
export * from "./widget_body";
|
export * from "./widget_body";
|
||||||
export * from "./fb_select";
|
|
||||||
export * from "./new_fb_select";
|
|
||||||
export * from "./fallback_img";
|
|
||||||
export * from "./doc_link";
|
|
||||||
export * from "./left_panel";
|
|
||||||
export * from "./center_panel";
|
|
||||||
export * from "./right_panel";
|
|
||||||
|
|
Loading…
Reference in New Issue