misc fixes

pull/1509/head
gabrielburnworth 2019-10-22 09:03:00 -07:00
parent 3871a0b083
commit 5d76123682
23 changed files with 113 additions and 58 deletions

View File

@ -157,8 +157,8 @@ export function fakeDiagnosticDump(): TaggedDiagnosticDump {
firmware_state: string,
network_interface: string,
fbos_dmesg_dump: string,
created_at: string,
updated_at: string,
created_at: "2018-01-11T20:20:38.362Z",
updated_at: "2018-01-11T20:20:38.362Z",
});
}

View File

@ -0,0 +1,12 @@
import { onLogs } from "../log_handlers";
import { Log } from "farmbot/dist/resources/api_resources";
describe("onLogs()", () => {
it("inits log", () => {
const msg = { message: "test log", type: undefined, channels: [] };
const log = onLogs(jest.fn(), jest.fn())(msg as unknown as Log);
expect(log).toEqual(expect.objectContaining({
body: expect.objectContaining({ type: "info" })
}));
});
});

View File

@ -12,6 +12,7 @@ import { globalQueue } from "./batch_queue";
export const onLogs =
(_dispatch: Function, getState: GetState) => (msg: Log) => {
if (isLog(msg)) {
!msg.type && (msg.type = "info");
actOnChannelName(msg, "toast", showLogOnScreen);
actOnChannelName(msg, "espeak", speakLogAloud(getState));
const log = initLog(msg).payload;

View File

@ -77,16 +77,6 @@ export class FarmbotOsSettings
const { bot, sourceFbosConfig, botToMqttStatus } = this.props;
const { sync_status } = bot.hardware.informational_settings;
const botOnline = isBotOnline(sync_status, botToMqttStatus);
const bootRow = <Row>
<Col xs={ColWidth.label}>
<label>
{t("BOOT SEQUENCE")}
</label>
</Col>
<Col xs={7}>
<BootSequenceSelector />
</Col>
</Row>;
return <Widget className="device-widget">
<form onSubmit={(e) => e.preventDefault()}>
<WidgetHeader title="Device">
@ -160,7 +150,8 @@ export class FarmbotOsSettings
shouldDisplay={this.props.shouldDisplay}
timeSettings={this.props.timeSettings}
sourceFbosConfig={sourceFbosConfig} />
{this.props.shouldDisplay(Feature.boot_sequence) && bootRow}
{this.props.shouldDisplay(Feature.boot_sequence) &&
<BootSequenceSelector />}
<PowerAndReset
controlPanelState={this.props.bot.controlPanelState}
dispatch={this.props.dispatch}

View File

@ -2,11 +2,13 @@ import * as React from "react";
import { connect } from "react-redux";
import { Everything } from "../../../interfaces";
import { getFbosConfig } from "../../../resources/getters";
import { FBSelect, DropDownItem } from "../../../ui";
import { FBSelect, DropDownItem, Row, Col } from "../../../ui";
import { edit, save } from "../../../api/crud";
import { TaggedFbosConfig, TaggedSequence } from "farmbot";
import { selectAllSequences, findSequenceById } from "../../../resources/selectors";
import { betterCompact } from "../../../util";
import { ColWidth } from "../farmbot_os_settings";
import { t } from "../../../i18next_wrapper";
interface Props {
list: DropDownItem[];
@ -53,13 +55,20 @@ export class RawBootSequenceSelector extends React.Component<Props, {}> {
}
render() {
return <div>
<FBSelect
allowEmpty={true}
list={this.props.list}
selectedItem={this.props.selectedItem}
onChange={this.onChange} />
</div>;
return <Row>
<Col xs={ColWidth.label}>
<label>
{t("BOOT SEQUENCE")}
</label>
</Col>
<Col xs={7}>
<FBSelect
allowEmpty={true}
list={this.props.list}
selectedItem={this.props.selectedItem}
onChange={this.onChange} />
</Col>
</Row>;
}
}

View File

@ -94,22 +94,25 @@ export function DesignerNavTabs(props: { hidden?: boolean }) {
panel={Panel.Groups}
linkTo={"/app/designer/groups"}
title={t("Groups")} />
<NavTab
panel={Panel.SavedGardens}
linkTo={"/app/designer/saved_gardens"}
title={t("Gardens")} />
{DevSettings.futureFeaturesEnabled() &&
<NavTab
panel={Panel.SavedGardens}
linkTo={"/app/designer/saved_gardens"}
title={t("Gardens")} />}
<NavTab
panel={Panel.FarmEvents}
linkTo={"/app/designer/events"}
title={t("Events")} />
{DevSettings.futureFeaturesEnabled() && <NavTab
panel={Panel.Points}
linkTo={"/app/designer/points"}
title={t("Points")} />}
{DevSettings.futureFeaturesEnabled() && <NavTab
panel={Panel.Tools}
linkTo={"/app/designer/tools"}
title={t("Tools")} />}
{DevSettings.futureFeaturesEnabled() &&
<NavTab
panel={Panel.Points}
linkTo={"/app/designer/points"}
title={t("Points")} />}
{DevSettings.futureFeaturesEnabled() &&
<NavTab
panel={Panel.Tools}
linkTo={"/app/designer/tools"}
title={t("Tools")} />}
<NavTab
panel={Panel.Settings}
icon={"fa fa-gear"}

View File

@ -46,7 +46,7 @@ export class RawEditPoint extends React.Component<EditPointProps, {}> {
<ul>
{
Object.entries(body.meta).map(([k, v]) => {
return <li>{k}: {v}</li>;
return <li key={k}>{k}: {v}</li>;
})
}
</ul>

View File

@ -4,12 +4,12 @@ mockGroup.body.point_ids = [1, 2, 3];
jest.mock("../group_detail", () => ({ fetchGroupFromUrl: () => mockGroup }));
import * as React from "react";
import { mount } from "enzyme";
import { GroupOrder, GroupOrderProps } from "../group_order_visual";
import {
fakeMapTransformProps
} from "../../../__test_support__/map_transform_props";
import { fakePlant } from "../../../__test_support__/fake_state/resources";
import { svgMount } from "../../../__test_support__/svg_mount";
describe("<GroupOrder />", () => {
const fakeProps = (): GroupOrderProps => {
@ -30,7 +30,7 @@ describe("<GroupOrder />", () => {
};
it("renders group order", () => {
const wrapper = mount(<GroupOrder {...fakeProps()} />);
const wrapper = svgMount(<GroupOrder {...fakeProps()} />);
expect(wrapper.find("line").length).toEqual(3);
});
});

View File

@ -14,7 +14,6 @@ describe("AdditionalMenu", () => {
close={jest.fn()} />);
const text = wrapper.text();
expect(text).toContain("Account Settings");
expect(text).toContain("Documentation");
expect(text).toContain("Logout");
expect(text).toContain("VERSION");
});
@ -33,7 +32,7 @@ describe("AdditionalMenu", () => {
const wrapper = shallow(<AdditionalMenu
logout={logout}
close={jest.fn()} />);
wrapper.find("a").at(1).simulate("click");
wrapper.find("a").at(0).simulate("click");
expect(logout).toHaveBeenCalled();
});

View File

@ -1,8 +1,18 @@
let mockPath = "/app/designer/plants";
jest.mock("../../history", () => ({
getPathArray: jest.fn(() => mockPath.split("/")),
}));
let mockDev = false;
jest.mock("../../account/dev/dev_support", () => ({
DevSettings: { futureFeaturesEnabled: () => mockDev }
}));
import * as React from "react";
import { shallow, mount } from "enzyme";
import { NavLinks } from "../nav_links";
describe("NavLinks", () => {
describe("<NavLinks />", () => {
it("toggles the mobile nav menu", () => {
const close = jest.fn();
const wrapper = shallow(<NavLinks close={(x) => () => close(x)}
@ -16,4 +26,16 @@ describe("NavLinks", () => {
const wrapper = mount(<NavLinks close={jest.fn()} alertCount={1} />);
expect(wrapper.text()).toContain("1");
});
it("shows links", () => {
mockDev = true;
const wrapper = mount(<NavLinks close={jest.fn()} alertCount={1} />);
expect(wrapper.text().toLowerCase()).not.toContain("tools");
});
it("shows active link", () => {
mockPath = "/app/designer";
const wrapper = shallow(<NavLinks close={jest.fn()} alertCount={1} />);
expect(wrapper.find("Link").first().hasClass("active")).toBeTruthy();
});
});

View File

@ -1,6 +1,5 @@
import * as React from "react";
import { AccountMenuProps } from "./interfaces";
import { docLink } from "../ui/doc_link";
import { Link } from "../link";
import { shortRevision } from "../util";
import { t } from "../i18next_wrapper";
@ -23,12 +22,6 @@ export const AdditionalMenu = (props: AccountMenuProps) => {
<i className="fa fa-question-circle"></i>
{t("Help")}
</Link>
<div>
<a href={docLink("the-farmbot-web-app")}
target="_blank">
<i className="fa fa-file-text-o"></i>{t("Documentation")}
</a>
</div>
<div>
<a onClick={props.logout}>
<i className="fa fa-sign-out"></i>

View File

@ -7,6 +7,7 @@ import {
import { Link } from "../link";
import { t } from "../i18next_wrapper";
import { betterCompact } from "../util";
import { DevSettings } from "../account/dev/dev_support";
/** Uses a slug and a child path to compute the `href` of a navbar link. */
export type LinkComputeFn = (slug: string, childPath: string) => string;
@ -36,7 +37,8 @@ export const getLinks = (): NavLinkParams[] => betterCompact([
name: "Regimens", icon: "calendar-check-o", slug: "regimens",
computeHref: computeEditorUrlFromState("Regimen")
},
{ name: "Tools", icon: "wrench", slug: "tools" },
DevSettings.futureFeaturesEnabled() ? undefined :
{ name: "Tools", icon: "wrench", slug: "tools" },
{
name: "Farmware", icon: "crosshairs", slug: "farmware",
computeHref: computeFarmwareUrlFromState

View File

@ -39,7 +39,6 @@ describe("<LocalsList/>", () => {
onChange: jest.fn(),
shouldDisplay: jest.fn(),
allowedVariableNodes: AllowedVariableNodes.parameter,
customFilterRule: undefined
};
};

View File

@ -8,7 +8,9 @@ import {
describe("locationFormList()", () => {
it("returns dropdown list", () => {
const items = locationFormList(fakeResourceIndex(), []);
const pg = fakePointGroup();
pg.body.id = 1;
const items = locationFormList(fakeResourceIndex([pg]), [], true);
const coordinate = items[0];
expect(coordinate).toEqual({
headingId: "Coordinate",
@ -54,6 +56,19 @@ describe("locationFormList()", () => {
label: "Point 1 (10, 20, 30)",
value: "2"
});
const groupHeading = items[8];
expect(groupHeading).toEqual({
headingId: "PointGroup",
label: "Groups",
value: 0,
heading: true,
});
const group = items[9];
expect(group).toEqual({
headingId: "PointGroup",
label: "Fake",
value: "1"
});
});
});

View File

@ -34,18 +34,19 @@ describe("<LocationForm/>", () => {
onChange: jest.fn(),
shouldDisplay: jest.fn(),
allowedVariableNodes: AllowedVariableNodes.parameter,
customFilterRule: undefined
});
it("renders correct UI components", () => {
const p = fakeProps();
p.shouldDisplay = () => true;
const el = shallow(<LocationForm {...p} />);
const selects = el.find(FBSelect);
const inputs = el.find(BlurableInput);
expect(selects.length).toBe(1);
const select = selects.first().props();
const choices = locationFormList(p.resources, [PARENT("")]);
const choices = locationFormList(
p.resources, [PARENT("Externally defined")], true);
const actualLabels = select.list.map(x => x.label).sort();
const expectedLabels = choices.map(x => x.label).sort();
const diff = difference(actualLabels, expectedLabels);

View File

@ -35,6 +35,7 @@ export const DefaultValueForm = (props: DefaultValueFormProps) => {
shouldDisplay={() => true}
allowedVariableNodes={AllowedVariableNodes.variable}
hideTypeLabel={true}
hideGroups={true}
onChange={change(props.onChange, props.variableNode)}
customFilterRule={NO_GROUPS} />
</div>;

View File

@ -60,6 +60,7 @@ export const LocalsList = (props: LocalsListProps) => {
collapsed={props.collapsed}
toggleVarShow={props.toggleVarShow}
onChange={props.onChange}
hideGroups={props.hideGroups}
customFilterRule={props.customFilterRule} />)}
</div>;
};

View File

@ -54,6 +54,8 @@ interface CommonProps {
collapsible?: boolean;
collapsed?: boolean;
toggleVarShow?: () => void;
/** Don't show groups in dropdown. */
hideGroups?: boolean;
/** Optional filter to allow removal of arbitrary dropdown items.
* Return `false` to omit an item from display. */
customFilterRule?: (ddi: DropDownItem) => boolean;

View File

@ -45,7 +45,7 @@ const maybeUseStepData = ({ resources, bodyVariables, variable, uuid }: {
export const LocationForm =
(props: LocationFormProps) => {
const { sequenceUuid, resources, bodyVariables, variable,
allowedVariableNodes } = props;
allowedVariableNodes, hideGroups } = props;
const { celeryNode, dropdown, vector } = maybeUseStepData({
resources, bodyVariables, variable, uuid: sequenceUuid
});
@ -55,7 +55,8 @@ export const LocationForm =
const variableListItems = displayVariables ? [PARENT(determineVarDDILabel({
label: "parent", resources, uuid: sequenceUuid, forceExternal: headerForm
}))] : [];
const unfiltered = locationFormList(resources, variableListItems);
const displayGroups = props.shouldDisplay(Feature.groups) && !hideGroups;
const unfiltered = locationFormList(resources, variableListItems, displayGroups);
const list = props.customFilterRule ?
unfiltered.filter(props.customFilterRule) : unfiltered;
/** Variable name. */

View File

@ -66,7 +66,7 @@ export const groups2Ddi = (groups: TaggedPointGroup[]): DropDownItem[] => {
/** Location selection menu items. */
export function locationFormList(resources: ResourceIndex,
additionalItems: DropDownItem[]): DropDownItem[] {
additionalItems: DropDownItem[], displayGroups?: boolean): DropDownItem[] {
const points = selectAllActivePoints(resources)
.filter(x => x.body.pointer_type !== "ToolSlot");
const plantDDI = points2ddi(points, "Plant");
@ -80,8 +80,8 @@ export function locationFormList(resources: ResourceIndex,
.concat(plantDDI)
.concat(heading("GenericPointer"))
.concat(genericPointerDDI)
.concat(heading("PointGroup"))
.concat(groups2Ddi(selectAllPointGroups(resources)));
.concat(displayGroups ? heading("PointGroup") : [])
.concat(displayGroups ? groups2Ddi(selectAllPointGroups(resources)) : []);
}
/** Create drop down item with label; i.e., "Point/Plant (1, 2, 3)" */

View File

@ -3,7 +3,7 @@ import { ResourceIndex } from "../../resources/interfaces";
import { TaggedResource } from "farmbot";
import { newTaggedResource } from "../../sync/actions";
export function fakeResourceIndex(): ResourceIndex {
export function fakeResourceIndex(extra: TaggedResource[] = []): ResourceIndex {
const fakeResources: TaggedResource[] = [
...newTaggedResource("Point", {
"id": 1,
@ -56,7 +56,8 @@ export function fakeResourceIndex(): ResourceIndex {
"id": 1,
"name": "Generic Tool",
"status": "active"
})
}),
...extra,
];
return buildResourceIndex(fakeResources).index;
}

View File

@ -212,6 +212,7 @@ const SequenceHeader = (props: SequenceHeaderProps) => {
collapsed={props.variablesCollapsed}
toggleVarShow={props.toggleVarShow}
shouldDisplay={props.shouldDisplay}
hideGroups={true}
customFilterRule={NO_GROUPS} />
</div>;
};

View File

@ -95,6 +95,7 @@ export class TileMoveAbsolute extends React.Component<StepParams, MoveAbsState>
this.updateLocation(x)}
shouldDisplay={this.props.shouldDisplay || (() => false)}
hideHeader={true}
hideGroups={true}
locationDropdownKey={JSON.stringify(this.props.currentSequence)}
allowedVariableNodes={AllowedVariableNodes.identifier}
width={3}