Skip to content

Commit

Permalink
add 3D electronics box top UI
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielburnworth committed Jan 17, 2024
1 parent 4a1dc1d commit b050710
Show file tree
Hide file tree
Showing 19 changed files with 1,087 additions and 171 deletions.
1 change: 1 addition & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class Application < Rails::Application
"'unsafe-inline'",
"'unsafe-eval'",
"'self'",
"blob:", # 3D
],
style_src: %w(
maxcdn.bootstrapcdn.com
Expand Down
7 changes: 7 additions & 0 deletions frontend/__test_support__/additional_mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ jest.mock("../history", () => ({
push: jest.fn(),
getPathArray: () => [],
}));

window.ResizeObserver = (() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
})) as any;
238 changes: 238 additions & 0 deletions frontend/controls/peripherals/__tests__/model_test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
jest.mock("@react-three/drei", () => {
const useGLTF = jest.fn(() => ({
nodes: {
Electronics_Box: {
geometry: jest.fn(),
material: { color: { set: jest.fn() } },
},
Electronics_Box_Gasket: {
geometry: jest.fn(),
material: { color: { set: jest.fn() } },
},
Electronics_Box_Lid: {
geometry: jest.fn(),
material: { color: { set: jest.fn() } },
},
["Push_Button_-_Red"]: {
geometry: jest.fn(),
material: { color: { set: jest.fn() } },
},
LED: {
geometry: jest.fn(),
material: { color: { set: jest.fn() } },
},
},
materials: {
[Material.box]: {
color: { set: jest.fn() },
transparent: false,
},
[Material.gasket]: {
color: { set: jest.fn() },
transparent: false,
},
[Material.lid]: {
color: { set: jest.fn() },
transparent: false,
},
[Material.button]: {
color: { set: jest.fn() },
transparent: false,
},
[Material.led]: {
color: { set: jest.fn() },
transparent: false,
},
},
}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(useGLTF as any).preload = jest.fn();
return {
useGLTF,
Cylinder: (props: { name: string }) => <div id={props.name} />,
Html: ({ children }: { children: ReactNode }) => <div>{children}</div>,
PerspectiveCamera: () => <div />,
useCursor: jest.fn(),
};
});

let mockElapsedTime = 0;
jest.mock("@react-three/fiber", () => ({
Canvas: () => <div />,
ThreeEvent: jest.fn(),
useFrame: jest.fn(x => x({
clock: { getElapsedTime: jest.fn(() => mockElapsedTime) }
})),
}));

let mockName = "";
const mockSetColor = jest.fn();
jest.mock("react", () => {
const originReact = jest.requireActual("react");
const mockRef = jest.fn(() => ({
current: {
material: { color: { set: mockSetColor } },
name: mockName,
setOptions: jest.fn(),
}
}));
return {
...originReact,
useRef: mockRef,
};
});

const lodash = require("lodash");
lodash.debounce = jest.fn(x => x);

jest.mock("../../../devices/actions", () => ({
execSequence: jest.fn(),
}));

import React, { ReactNode } from "react";
import { mount } from "enzyme";
import { ThreeEvent } from "@react-three/fiber";
import {
ElectronicsBoxModelProps, IColor, Material, Model, setZForAllInGroup,
} from "../model";
import {
buildResourceIndex,
} from "../../../__test_support__/resource_index_builder";
import {
StandardPinBinding,
} from "farmbot/dist/resources/api_resources";
import {
fakePinBinding, fakeSequence,
} from "../../../__test_support__/fake_state/resources";
import { bot } from "../../../__test_support__/fake_state/bot";
import { execSequence } from "../../../devices/actions";
import { ButtonPin } from "../../../settings/pin_bindings/list_and_label_support";

describe("setZForAllInGroup()", () => {
it("sets z", () => {
const e = {
object: {
parent: {
children: [
{ name: "button", position: { z: 0 }, children: [] },
]
}
}
} as unknown as ThreeEvent<PointerEvent>;
setZForAllInGroup(e, 100);
expect(e.object.parent?.children[0].position.z).toEqual(100);
});
});

describe("<ElectronicsBoxModel />", () => {
const fakeProps = (): ElectronicsBoxModelProps => {
const binding = fakePinBinding();
binding.body.pin_num = ButtonPin.estop;
(binding.body as StandardPinBinding).sequence_id = 1;
const sequence = fakeSequence();
sequence.body.id = 1;
sequence.body.name = "e-stop";
return {
isEditing: false,
dispatch: jest.fn(),
resources: buildResourceIndex([binding, sequence]).index,
botOnline: true,
bot,
firmwareHardware: "arduino",
};
};

const e = {
object: {
parent: {
children: [
{ name: "button", position: { z: 0 }, children: [] },
]
}
}
};

it("triggers binding", () => {
const p = fakeProps();
p.isEditing = false;
p.botOnline = true;
const wrapper = mount(<Model {...p} />);
wrapper.find({ name: "action-group" }).first().simulate("pointerdown", e);
expect(execSequence).toHaveBeenCalledWith(1);
});

it("hovers button", () => {
const wrapper = mount(<Model {...fakeProps()} />);
const btnBefore = wrapper.find({ name: "button" }).first();
expect(btnBefore.props()["material-color"]).toEqual(13421772);
wrapper.find({ name: "action-group" }).first().simulate("pointerover", e);
const btnAfter = wrapper.find({ name: "button" }).first();
expect(btnAfter.props()["material-color"]).toEqual(14540253);
expect(e.object.parent?.children[0].position.z).toEqual(128);
});

it("un-hovers button", () => {
const wrapper = mount(<Model {...fakeProps()} />);
wrapper.find({ name: "action-group" }).first().simulate("pointerout", e);
expect(e.object.parent?.children[0].position.z).toEqual(131);
});

it("resets z", () => {
const wrapper = mount(<Model {...fakeProps()} />);
wrapper.find({ name: "button-group" }).first().simulate("pointerup", e);
expect(e.object.parent?.children[0].position.z).toEqual(131);
});

it("renders: off", () => {
const p = fakeProps();
p.isEditing = true;
p.botOnline = false;
p.bot.hardware.informational_settings.locked = false;
const sequence = fakeSequence();
p.resources = buildResourceIndex([sequence]).index;
const wrapper = mount(<Model {...p} />);
expect(wrapper.html()).not.toContain("blink");
expect(mockSetColor).toHaveBeenCalledWith(IColor.estop.off);
});

it("renders: on", () => {
mockName = "on";
const p = fakeProps();
p.botOnline = true;
p.bot.hardware.informational_settings.locked = false;
p.bot.hardware.informational_settings.busy = false;
const wrapper = mount(<Model {...p} />);
expect(wrapper.html()).toContain("on");
expect(mockSetColor).toHaveBeenCalledWith(IColor.estop.on);
});

it("renders: blinking on", () => {
mockName = "blink";
const p = fakeProps();
p.isEditing = true;
p.bot.hardware.informational_settings.locked = true;
p.bot.hardware.informational_settings.sync_status = "syncing";
const wrapper = mount(<Model {...p} />);
expect(wrapper.html()).toContain("blink");
expect(mockSetColor).toHaveBeenCalledWith(IColor.unlock.on);
});

it("renders: blinking off", () => {
mockElapsedTime = 1;
mockName = "blink";
const p = fakeProps();
p.bot.hardware.informational_settings.locked = true;
const wrapper = mount(<Model {...p} />);
expect(wrapper.html()).toContain("blink");
expect(mockSetColor).toHaveBeenCalledWith(IColor.unlock.off);
});

it("renders: express", () => {
mockElapsedTime = 1;
mockName = "blink";
const p = fakeProps();
p.firmwareHardware = "express_k10";
const wrapper = mount(<Model {...p} />);
expect(wrapper.html().toLowerCase()).toContain("e-stop");
});
});
26 changes: 16 additions & 10 deletions frontend/controls/peripherals/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { Content } from "../../constants";
import { uniq, isNumber } from "lodash";
import { t } from "../../i18next_wrapper";
import { DIGITAL } from "farmbot";
import { isBotOnlineFromState } from "../../devices/must_be_online";
import { BoxTopButtons } from "../../settings/pin_bindings/box_top_gpio_diagram";
import { isBotOnline } from "../../devices/must_be_online";
import { ElectronicsBoxModel } from "./model";
import { getStatus } from "../../connectivity/reducer_support";

export class Peripherals
extends React.Component<PeripheralsProps, PeripheralState> {
Expand All @@ -20,9 +21,16 @@ export class Peripherals
this.state = { isEditing: false };
}

get botOnline() {
const { hardware, connectivity } = this.props.bot;
const { sync_status } = hardware.informational_settings;
const botToMqttStatus = getStatus(connectivity.uptime["bot.mqtt"]);
return isBotOnline(sync_status, botToMqttStatus);
}

get disabled() {
return !!this.props.bot.hardware.informational_settings.busy
|| !isBotOnlineFromState(this.props.bot);
|| !this.botOnline;
}

toggle = () => this.setState({ isEditing: !this.state.isEditing });
Expand Down Expand Up @@ -114,15 +122,13 @@ export class Peripherals
: t("Edit");
return <div className={"peripherals-widget"}>
{!this.props.hidePinBindings &&
<BoxTopButtons
firmwareHardware={this.props.firmwareHardware}
<ElectronicsBoxModel
isEditing={isEditing}
dispatch={this.props.dispatch}
resources={this.props.resources}
botOnline={isBotOnlineFromState(this.props.bot)}
syncStatus={this.props.bot.hardware.informational_settings.sync_status}
locked={this.props.bot.hardware.informational_settings.locked}
isEditing={isEditing} />}
<hr />
firmwareHardware={this.props.firmwareHardware}
bot={this.props.bot}
botOnline={this.botOnline} />}
<EmptyStateWrapper
notEmpty={this.props.peripherals.length > 0 || isEditing}
graphic={EmptyStateGraphic.regimens}
Expand Down

0 comments on commit b050710

Please sign in to comment.