misc fixes

pull/1158/head
gabrielburnworth 2019-04-17 12:30:58 -07:00
parent 83d9cb8287
commit cb44640ca5
22 changed files with 143 additions and 78 deletions

View File

@ -1,8 +1,3 @@
let mockDev = false;
jest.mock("../account/dev/dev_support", () => ({
DevSettings: { futureFeaturesEnabled: () => mockDev }
}));
jest.mock("react-redux", () => ({ connect: jest.fn() }));
let mockPath = "";
@ -16,7 +11,7 @@ import { App, AppProps, mapStateToProps } from "../app";
import { mount } from "enzyme";
import { bot } from "../__test_support__/fake_state/bot";
import {
fakeUser, fakeWebAppConfig, fakeEnigma
fakeUser, fakeWebAppConfig
} from "../__test_support__/fake_state/resources";
import { fakeState } from "../__test_support__/fake_state";
import {
@ -147,21 +142,4 @@ describe("mapStateToProps()", () => {
const result = mapStateToProps(state);
expect(result.axisInversion.x).toEqual(true);
});
it("doesn't show API alerts", () => {
const state = fakeState();
state.resources = buildResourceIndex([fakeEnigma()]);
mockDev = false;
const props = mapStateToProps(state);
expect(props.alertCount).toEqual(0);
});
it("shows API alerts", () => {
const state = fakeState();
const enigma = fakeEnigma();
state.resources = buildResourceIndex([enigma]);
mockDev = true;
const props = mapStateToProps(state);
expect(props.alertCount).toEqual(1);
});
});

View File

@ -10,12 +10,11 @@ import {
maybeFetchUser,
maybeGetTimeSettings,
getDeviceAccountSettings,
selectAllEnigmas,
} from "./resources/selectors";
import { HotKeys } from "./hotkeys";
import { ControlsPopup } from "./controls_popup";
import { Content } from "./constants";
import { validBotLocationData, validFwConfig, validFbosConfig, betterCompact } from "./util";
import { validBotLocationData, validFwConfig, validFbosConfig } from "./util";
import { BooleanSetting } from "./session_keys";
import { getPathArray } from "./history";
import {
@ -29,7 +28,7 @@ import { t } from "./i18next_wrapper";
import { ResourceIndex } from "./resources/interfaces";
import { isBotOnline } from "./devices/must_be_online";
import { getStatus } from "./connectivity/reducer_support";
import { DevSettings } from "./account/dev/dev_support";
import { getAlerts } from "./messages/state_to_props";
/** For the logger module */
init();
@ -56,10 +55,6 @@ export interface AppProps {
export function mapStateToProps(props: Everything): AppProps {
const webAppConfigValue = getWebAppConfigValue(() => props);
const fbosConfig = validFbosConfig(getFbosConfig(props.resources.index));
const botAlerts = betterCompact(Object.values(props.bot.hardware.enigmas || {}));
const apiAlerts = selectAllEnigmas(props.resources.index).map(x => x.body);
const alerts =
botAlerts.concat(DevSettings.futureFeaturesEnabled() ? apiAlerts : []);
return {
timeSettings: maybeGetTimeSettings(props.resources.index),
dispatch: props.dispatch,
@ -80,7 +75,7 @@ export function mapStateToProps(props: Everything): AppProps {
tour: props.resources.consumers.help.currentTour,
resources: props.resources.index,
autoSync: !!(fbosConfig && fbosConfig.auto_sync),
alertCount: alerts.length,
alertCount: getAlerts(props.resources.index, props.bot).length,
};
}
/** Time at which the app gives up and asks the user to refresh */

View File

@ -422,8 +422,7 @@ export namespace Content {
// App Settings
export const CONFIRM_STEP_DELETION =
trim(`Show a confirmation dialog when the sequence delete step
icon is pressed.`);
trim(`Show a confirmation dialog when deleting a sequence step.`);
export const HIDE_WEBCAM_WIDGET =
trim(`If not using a webcam, use this setting to remove the

View File

@ -13,7 +13,7 @@ describe("calcMicrostepsPerMm()", () => {
});
it("calculates value with microstepping", () => {
expect(calcMicrostepsPerMm(5, 4)).toEqual(20);
expect(calcMicrostepsPerMm(5, 4)).toEqual(5);
});
});
@ -30,7 +30,7 @@ describe("calculateAxialLengths()", () => {
firmwareSettings.movement_step_per_mm_z = 25;
firmwareSettings.movement_microsteps_z = 4;
expect(calculateAxialLengths({ firmwareSettings })).toEqual({
x: 0, y: 20, z: 1
x: 0, y: 20, z: 4
});
});
});

View File

@ -4,7 +4,8 @@ import { McuParams } from "farmbot";
export const calcMicrostepsPerMm = (
steps_per_mm: number | undefined,
microsteps_per_step: number | undefined) =>
(steps_per_mm || 1) * (microsteps_per_step || 1);
// The firmware currently interprets steps_per_mm as microsteps_per_mm.
(steps_per_mm || 1) * (1 || microsteps_per_step || 1);
const calcAxisLength = (
nr_steps: number | undefined,

View File

@ -26,9 +26,6 @@
0% {
transform: translateX(-11rem)
}
90% {
transform: translateX(1rem)
}
100% {
transform: translateX(0)
}

View File

@ -100,6 +100,7 @@ nav {
}
div {
display: inline;
vertical-align: middle;
}
}
}

View File

@ -76,6 +76,9 @@ export function EncodersAndEndStops(props: EncodersProps) {
x={"encoder_scaling_x"}
y={"encoder_scaling_y"}
z={"encoder_scaling_z"}
xScale={sourceFwConfig("movement_microsteps_x").value}
yScale={sourceFwConfig("movement_microsteps_y").value}
zScale={sourceFwConfig("movement_microsteps_z").value}
intSize={shouldDisplay(Feature.long_scaling_factor) ? "long" : "short"}
gray={encodersDisabled}
sourceFwConfig={sourceFwConfig}

View File

@ -37,7 +37,7 @@ describe("<Tour />", () => {
const wrapper = shallow<Tour>(<Tour steps={steps} />);
wrapper.instance().callback(fakeCallbackData({ type: "tour:end" }));
expect(wrapper.state()).toEqual({ run: false, index: 0 });
expect(history.push).toHaveBeenCalledWith("/app/help");
expect(history.push).toHaveBeenCalledWith("/app/messages");
});
it("navigates through tour: next", () => {

View File

@ -1,5 +1,4 @@
import * as React from "react";
import Joyride, { Step as TourStep, CallBackProps } from "react-joyride";
import { Color } from "../ui";
import { history } from "../history";
@ -44,7 +43,7 @@ export class Tour extends React.Component<TourProps, TourState> {
}
if (type === "tour:end") {
this.setState({ run: false });
history.push("/app/help");
history.push("/app/messages");
}
};

View File

@ -39,6 +39,7 @@ describe("<Alerts />", () => {
apiFirmwareValue: undefined,
timeSettings: fakeTimeSettings(),
dispatch: jest.fn(),
findApiAlertById: jest.fn(),
});
it("renders no alerts", () => {

View File

@ -1,9 +1,12 @@
jest.mock("../../api/crud", () => ({ destroy: jest.fn() }));
import * as React from "react";
import { mount } from "enzyme";
import { AlertCard } from "../cards";
import { AlertCardProps } from "../interfaces";
import { fakeTimeSettings } from "../../__test_support__/fake_time_settings";
import { FBSelect } from "../../ui";
import { destroy } from "../../api/crud";
describe("<AlertCard />", () => {
const fakeProps = (): AlertCardProps => ({
@ -19,8 +22,13 @@ describe("<AlertCard />", () => {
});
it("renders unknown card", () => {
const wrapper = mount(<AlertCard {...fakeProps()} />);
const p = fakeProps();
p.alert.id = 1;
p.findApiAlertById = () => "uuid";
const wrapper = mount(<AlertCard {...p} />);
expect(wrapper.text()).toContain("noun: verb (author)");
wrapper.find(".fa-times").simulate("click");
expect(destroy).toHaveBeenCalledWith("uuid");
});
it("renders firmware card", () => {
@ -28,6 +36,8 @@ describe("<AlertCard />", () => {
p.alert.problem_tag = "farmbot_os.firmware.missing";
const wrapper = mount(<AlertCard {...p} />);
expect(wrapper.text()).toContain("Firmware missing");
wrapper.find(".fa-times").simulate("click");
expect(destroy).not.toHaveBeenCalled();
});
it("renders seed data card", () => {

View File

@ -12,6 +12,7 @@ describe("<Messages />", () => {
apiFirmwareValue: undefined,
timeSettings: fakeTimeSettings(),
dispatch: Function,
findApiAlertById: jest.fn(),
});
it("renders page", () => {

View File

@ -6,7 +6,9 @@ jest.mock("../../account/dev/dev_support", () => ({
import { fakeState } from "../../__test_support__/fake_state";
import { mapStateToProps } from "../state_to_props";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
import { fakeEnigma, fakeFbosConfig } from "../../__test_support__/fake_state/resources";
import {
fakeEnigma, fakeFbosConfig
} from "../../__test_support__/fake_state/resources";
describe("mapStateToProps()", () => {
it("handles undefined", () => {
@ -18,7 +20,9 @@ describe("mapStateToProps()", () => {
it("doesn't show API alerts", () => {
const state = fakeState();
state.resources = buildResourceIndex([fakeEnigma()]);
const enigma = fakeEnigma();
enigma.body.problem_tag = "api.seed_data.missing";
state.resources = buildResourceIndex([enigma]);
mockDev = false;
const props = mapStateToProps(state);
expect(props.alerts).toEqual([]);
@ -27,6 +31,7 @@ describe("mapStateToProps()", () => {
it("shows API alerts", () => {
const state = fakeState();
const enigma = fakeEnigma();
enigma.body.problem_tag = "api.seed_data.missing";
state.resources = buildResourceIndex([enigma]);
mockDev = true;
const props = mapStateToProps(state);
@ -42,4 +47,13 @@ describe("mapStateToProps()", () => {
const props = mapStateToProps(state);
expect(props.apiFirmwareValue).toEqual("arduino");
});
it("finds alert", () => {
const state = fakeState();
const alert = fakeEnigma();
alert.body.id = 1;
state.resources = buildResourceIndex([alert]);
const props = mapStateToProps(state);
expect(props.findApiAlertById(1)).toEqual(alert.uuid);
});
});

View File

@ -37,6 +37,7 @@ export const Alerts = (props: AlertsProps) =>
alert={x}
dispatch={props.dispatch}
apiFirmwareValue={props.apiFirmwareValue}
timeSettings={props.timeSettings} />)}
timeSettings={props.timeSettings}
findApiAlertById={props.findApiAlertById} />)}
</div>
</div>;

View File

@ -3,7 +3,8 @@ import { t } from "../i18next_wrapper";
import {
AlertCardProps, AlertCardTemplateProps, FirmwareMissingProps,
SeedDataMissingProps, SeedDataMissingState, TourNotTakenProps,
CommonAlertCardProps
CommonAlertCardProps,
DismissAlertProps
} from "./interfaces";
import { formatLogTime } from "../logs";
import {
@ -13,20 +14,21 @@ import { DropDownItem, Row, Col, FBSelect, docLink } from "../ui";
import { Content } from "../constants";
import { TourList } from "../help/tour_list";
import { splitProblemTag } from "./alerts";
import { destroy } from "../api/crud";
export const AlertCard = (props: AlertCardProps) => {
const { alert, timeSettings } = props;
const commonProps = { alert, timeSettings };
const { alert, timeSettings, findApiAlertById, dispatch } = props;
const commonProps = { alert, timeSettings, findApiAlertById, dispatch };
switch (alert.problem_tag) {
case "farmbot_os.firmware.missing":
return <FirmwareMissing {...commonProps}
apiFirmwareValue={props.apiFirmwareValue} />;
case "api.seed_data.missing":
return <SeedDataMissing {...commonProps}
dispatch={props.dispatch} />;
dispatch={dispatch} />;
case "api.tour.not_taken":
return <TourNotTaken {...commonProps}
dispatch={props.dispatch} />;
dispatch={dispatch} />;
case "api.user.not_welcomed":
return <UserNotWelcomed {...commonProps} />;
case "api.documentation.unread":
@ -35,20 +37,27 @@ export const AlertCard = (props: AlertCardProps) => {
return UnknownAlert(commonProps);
}
};
const dismissAlert = (props: DismissAlertProps) => () =>
(props.id && props.findApiAlertById && props.dispatch)
? props.dispatch(destroy(props.findApiAlertById(props.id)))
: () => { };
const AlertCardTemplate = (props: AlertCardTemplateProps) =>
<div className={`problem-alert ${props.className}`}>
const AlertCardTemplate = (props: AlertCardTemplateProps) => {
const { alert, findApiAlertById, dispatch } = props;
return <div className={`problem-alert ${props.className}`}>
<div className="problem-alert-title">
<i className="fa fa-exclamation-triangle" />
<h3>{t(props.title)}</h3>
<p>{formatLogTime(props.alert.created_at, props.timeSettings)}</p>
<i className="fa fa-times" />
<p>{formatLogTime(alert.created_at, props.timeSettings)}</p>
<i className="fa fa-times"
onClick={dismissAlert({ id: alert.id, findApiAlertById, dispatch })} />
</div>
<div className="problem-alert-content">
<p>{t(props.message)}</p>
{props.children}
</div>
</div>;
};
const UnknownAlert = (props: CommonAlertCardProps) => {
const { problem_tag, created_at, priority } = props.alert;
@ -60,7 +69,9 @@ const UnknownAlert = (props: CommonAlertCardProps) => {
title={`${t(noun)}: ${t(verb)} (${t(author)})`}
message={t("Unknown problem of priority {{priority}} created at {{createdAt}}",
{ priority, createdAt })}
timeSettings={props.timeSettings} />;
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById} />;
};
const FirmwareMissing = (props: FirmwareMissingProps) =>
@ -69,7 +80,9 @@ const FirmwareMissing = (props: FirmwareMissingProps) =>
className={"firmware-missing-alert"}
title={t("Firmware missing")}
message={t("Your device has no firmware installed.")}
timeSettings={props.timeSettings}>
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById}>
<FirmwareActions
apiFirmwareValue={props.apiFirmwareValue}
botOnline={true} />
@ -92,7 +105,9 @@ class SeedDataMissing
className={"seed-data-missing-alert"}
title={t("Choose your FarmBot")}
message={t(Content.SEED_DATA_SELECTION)}
timeSettings={this.props.timeSettings}>
timeSettings={this.props.timeSettings}
dispatch={this.props.dispatch}
findApiAlertById={this.props.findApiAlertById}>
<Row>
<Col xs={4}>
<label>{t("Choose your FarmBot")}</label>
@ -115,7 +130,9 @@ const TourNotTaken = (props: TourNotTakenProps) =>
className={"tour-not-taken-alert"}
title={t("Take a guided tour")}
message={t(Content.TAKE_A_TOUR)}
timeSettings={props.timeSettings}>
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById}>
<p>{t("Choose a tour to begin")}:</p>
<TourList dispatch={props.dispatch} />
</AlertCardTemplate>;
@ -126,7 +143,9 @@ const UserNotWelcomed = (props: CommonAlertCardProps) =>
className={"user-not-welcomed-alert"}
title={t("Welcome to the FarmBot Web App")}
message={t(Content.WELCOME)}
timeSettings={props.timeSettings}>
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById}>
<p>
{t("You're currently viewing the")} <b>{t("Message Center")}</b>.
&nbsp;{t(Content.MESSAGE_CENTER_WELCOME)}
@ -142,7 +161,9 @@ const DocumentationUnread = (props: CommonAlertCardProps) =>
className={"documentation-unread-alert"}
title={t("Learn more about the app")}
message={t(Content.READ_THE_DOCS)}
timeSettings={props.timeSettings}>
timeSettings={props.timeSettings}
dispatch={props.dispatch}
findApiAlertById={props.findApiAlertById}>
<p>
{t("Head over to")}
&nbsp;<a href={docLink()} target="_blank"

View File

@ -24,7 +24,8 @@ export class Messages extends React.Component<MessagesProps, {}> {
<Alerts alerts={this.props.alerts}
dispatch={this.props.dispatch}
apiFirmwareValue={this.props.apiFirmwareValue}
timeSettings={this.props.timeSettings} />
timeSettings={this.props.timeSettings}
findApiAlertById={this.props.findApiAlertById} />
</Row>
<Row>
<div className="link-to-logs">

View File

@ -1,12 +1,14 @@
import { FirmwareHardware, Enigma } from "farmbot";
import { TimeSettings } from "../interfaces";
import { BotState } from "../devices/interfaces";
import { UUID } from "../resources/interfaces";
export interface MessagesProps {
alerts: Alert[];
apiFirmwareValue: FirmwareHardware | undefined;
timeSettings: TimeSettings;
dispatch: Function;
findApiAlertById(id: number): UUID;
}
export interface AlertsProps {
@ -14,6 +16,7 @@ export interface AlertsProps {
apiFirmwareValue: string | undefined;
timeSettings: TimeSettings;
dispatch: Function;
findApiAlertById(id: number): UUID;
}
export interface ProblemTag {
@ -36,11 +39,14 @@ export interface AlertCardProps {
apiFirmwareValue: string | undefined;
timeSettings: TimeSettings;
dispatch: Function;
findApiAlertById?(id: number): UUID;
}
export interface CommonAlertCardProps {
alert: Alert;
timeSettings: TimeSettings;
findApiAlertById?(id: number): UUID;
dispatch?: Function;
}
export interface AlertCardTemplateProps {
@ -50,6 +56,14 @@ export interface AlertCardTemplateProps {
message: string;
timeSettings: TimeSettings;
children?: React.ReactNode;
findApiAlertById?(id: number): UUID;
dispatch?: Function;
}
export interface DismissAlertProps {
id?: number;
findApiAlertById?(id: number): UUID;
dispatch?: Function;
}
export interface FirmwareMissingProps extends CommonAlertCardProps {

View File

@ -1,11 +1,15 @@
import { Everything } from "../interfaces";
import { MessagesProps } from "./interfaces";
import { MessagesProps, Alert } from "./interfaces";
import { validFbosConfig, betterCompact } from "../util";
import { getFbosConfig } from "../resources/getters";
import { sourceFbosConfigValue } from "../devices/components/source_config_value";
import { DevSettings } from "../account/dev/dev_support";
import { selectAllEnigmas, maybeGetTimeSettings } from "../resources/selectors";
import {
selectAllEnigmas, maybeGetTimeSettings, findResourceById
} from "../resources/selectors";
import { isFwHardwareValue } from "../devices/components/fbos_settings/board_type";
import { ResourceIndex, UUID } from "../resources/interfaces";
import { BotState } from "../devices/interfaces";
export const mapStateToProps = (props: Everything): MessagesProps => {
const { hardware } = props.bot;
@ -13,15 +17,23 @@ export const mapStateToProps = (props: Everything): MessagesProps => {
const sourceFbosConfig =
sourceFbosConfigValue(fbosConfig, hardware.configuration);
const apiFirmwareValue = sourceFbosConfig("firmware_hardware").value;
const botAlerts = betterCompact(Object.values(props.bot.hardware.enigmas || {}));
const apiAlerts = selectAllEnigmas(props.resources.index).map(x => x.body);
const alerts =
botAlerts.concat(DevSettings.futureFeaturesEnabled() ? apiAlerts : []);
const findApiAlertById = (id: number): UUID =>
findResourceById(props.resources.index, "Enigma", id);
return {
alerts,
alerts: getAlerts(props.resources.index, props.bot),
apiFirmwareValue: isFwHardwareValue(apiFirmwareValue)
? apiFirmwareValue : undefined,
timeSettings: maybeGetTimeSettings(props.resources.index),
dispatch: props.dispatch,
findApiAlertById,
};
};
export const getAlerts =
(resourceIndex: ResourceIndex, bot: BotState): Alert[] => {
const botAlerts = betterCompact(Object.values(bot.hardware.enigmas || {}));
const apiAlerts = selectAllEnigmas(resourceIndex).map(x => x.body)
.filter(x => DevSettings.futureFeaturesEnabled() ||
x.problem_tag !== "api.seed_data.missing");
return botAlerts.concat(apiAlerts);
};

View File

@ -1,7 +0,0 @@
import { stopIE } from "../stop_ie";
describe("stopIE()", () => {
it("not IE", () => {
expect(stopIE).not.toThrow();
});
});

View File

@ -0,0 +1,25 @@
import { updatePageInfo, attachToRoot } from "../page";
import React from "react";
describe("updatePageInfo()", () => {
it("sets page title", () => {
updatePageInfo("page name");
expect(document.title).toEqual("Page name - FarmBot");
});
it("sets page title: Farm Designer", () => {
updatePageInfo("designer");
expect(document.title).toEqual("Farm designer - FarmBot");
});
});
describe("attachToRoot()", () => {
class Foo extends React.Component<{ text: string }> {
render() { return <p>{this.props.text}</p>; }
}
it("attaches page", () => {
attachToRoot(Foo, { text: "Bar" });
expect(document.body.innerHTML).toEqual(`<div id="root"><p>Bar</p></div>`);
expect(document.body.textContent).toEqual("Bar");
});
});

View File

@ -4,14 +4,13 @@ import {
Attributes
} from "react";
import { render } from "react-dom";
import { capitalize } from "lodash";
import { t } from "../i18next_wrapper";
/** Dynamically change the meta title of the page. */
export function updatePageInfo(pageName: string) {
if (pageName === "designer") { pageName = "Farm Designer"; }
document.title = t(capitalize(pageName));
document.title = `${t(capitalize(pageName))} - FarmBot`;
// Possibly add meta "content" here dynamically as well
}