minor fixes
parent
0bd6d9a967
commit
6f484ab2e3
|
@ -7,4 +7,5 @@ jest.mock("../toast/toast", () => ({
|
|||
error: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
busy: jest.fn(),
|
||||
removeToast: jest.fn(),
|
||||
}));
|
||||
|
|
|
@ -37,7 +37,9 @@ import { getDevice } from "../../../device";
|
|||
import { talk } from "browser-speech";
|
||||
import { MessageType } from "../../../sequences/interfaces";
|
||||
import { FbjsEventName } from "farmbot/dist/constants";
|
||||
import { info, error, success, warning, fun, busy } from "../../../toast/toast";
|
||||
import {
|
||||
info, error, success, warning, fun, busy, removeToast,
|
||||
} from "../../../toast/toast";
|
||||
import { onLogs } from "../../log_handlers";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { globalQueue } from "../../batch_queue";
|
||||
|
@ -177,7 +179,8 @@ describe("onOffline", () => {
|
|||
jest.resetAllMocks();
|
||||
onOffline();
|
||||
expect(dispatchNetworkDown).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||
expect(error).toHaveBeenCalledWith(Content.MQTT_DISCONNECTED);
|
||||
expect(error).toHaveBeenCalledWith(
|
||||
Content.MQTT_DISCONNECTED, "Error", "red", "offline");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -186,13 +189,17 @@ describe("onOnline", () => {
|
|||
jest.resetAllMocks();
|
||||
onOnline();
|
||||
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
|
||||
expect(removeToast).toHaveBeenCalledWith("offline");
|
||||
});
|
||||
});
|
||||
|
||||
describe("onReconnect", () => {
|
||||
onReconnect();
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Attempting to reconnect to the message broker", "Offline", "yellow");
|
||||
describe("onReconnect()", () => {
|
||||
it("sends reconnect toast", () => {
|
||||
onReconnect();
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Attempting to reconnect to the message broker",
|
||||
"Offline", "yellow", "offline");
|
||||
});
|
||||
});
|
||||
|
||||
describe("changeLastClientConnected", () => {
|
||||
|
@ -268,7 +275,8 @@ describe("onPublicBroadcast", () => {
|
|||
console.log = jest.fn();
|
||||
onPublicBroadcast({});
|
||||
expectBroadcastLog();
|
||||
expect(window.alert).toHaveBeenCalledWith(Content.FORCE_REFRESH_CANCEL_WARNING);
|
||||
expect(window.alert).toHaveBeenCalledWith(
|
||||
Content.FORCE_REFRESH_CANCEL_WARNING);
|
||||
expect(location.assign).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,9 @@ import { Log } from "farmbot/dist/resources/api_resources";
|
|||
import { Farmbot, BotStateTree, TaggedResource } from "farmbot";
|
||||
import { FbjsEventName } from "farmbot/dist/constants";
|
||||
import { noop } from "lodash";
|
||||
import { success, error, info, warning, fun, busy } from "../toast/toast";
|
||||
import {
|
||||
success, error, info, warning, fun, busy, removeToast,
|
||||
} from "../toast/toast";
|
||||
import { HardwareState } from "../devices/interfaces";
|
||||
import { GetState, ReduxAction } from "../redux/interfaces";
|
||||
import { Content, Actions } from "../constants";
|
||||
|
@ -102,11 +104,6 @@ export function readStatus() {
|
|||
.then(() => { commandOK(noun); }, commandErr(noun));
|
||||
}
|
||||
|
||||
export const onOffline = () => {
|
||||
dispatchNetworkDown("user.mqtt", now());
|
||||
error(t(Content.MQTT_DISCONNECTED));
|
||||
};
|
||||
|
||||
export const changeLastClientConnected = (bot: Farmbot) => () => {
|
||||
bot.setUserEnv({
|
||||
"LAST_CLIENT_CONNECTED": JSON.stringify(new Date())
|
||||
|
@ -157,14 +154,20 @@ export function onMalformed() {
|
|||
}
|
||||
}
|
||||
|
||||
export const onOnline =
|
||||
() => {
|
||||
success(t("Reconnected to the message broker."), t("Online"));
|
||||
dispatchNetworkUp("user.mqtt", now());
|
||||
};
|
||||
export const onReconnect =
|
||||
() => warning(t("Attempting to reconnect to the message broker"),
|
||||
t("Offline"), "yellow");
|
||||
export const onOnline = () => {
|
||||
removeToast("offline");
|
||||
success(t("Reconnected to the message broker."), t("Online"));
|
||||
dispatchNetworkUp("user.mqtt", now());
|
||||
};
|
||||
|
||||
export const onReconnect = () =>
|
||||
warning(t("Attempting to reconnect to the message broker"),
|
||||
t("Offline"), "yellow", "offline");
|
||||
|
||||
export const onOffline = () => {
|
||||
dispatchNetworkDown("user.mqtt", now());
|
||||
error(t(Content.MQTT_DISCONNECTED), t("Error"), "red", "offline");
|
||||
};
|
||||
|
||||
export function onPublicBroadcast(payl: unknown) {
|
||||
console.log(FbjsEventName.publicBroadcast, payl);
|
||||
|
|
|
@ -291,10 +291,19 @@
|
|||
.panel-action-buttons {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
height: 25rem;
|
||||
height: 16rem;
|
||||
width: 100%;
|
||||
background: $panel_medium_light_gray;
|
||||
padding: 0.5rem;
|
||||
&.status {
|
||||
height: 20rem;
|
||||
}
|
||||
&.more {
|
||||
height: 23rem;
|
||||
}
|
||||
&.more.status {
|
||||
height: 26rem;
|
||||
}
|
||||
button {
|
||||
margin: 0.5rem;
|
||||
float: left;
|
||||
|
@ -303,11 +312,12 @@
|
|||
min-width: -webkit-fill-available;
|
||||
margin-bottom: 0px;
|
||||
margin-left: .5rem;
|
||||
margin-top: 1rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
.button-row {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.filter-search {
|
||||
padding-right: 1rem;
|
||||
|
@ -324,15 +334,35 @@
|
|||
line-height: 4.1rem;
|
||||
}
|
||||
}
|
||||
.more {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
margin-right: 1rem;
|
||||
line-height: 2.5rem;
|
||||
p {
|
||||
display: inline;
|
||||
font-size: 1.4rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.panel-content {
|
||||
padding-top: 25rem;
|
||||
padding-top: 16rem;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
padding-bottom: 5rem;
|
||||
max-height: calc(100vh - 13rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
&.status {
|
||||
padding-top: 20rem;
|
||||
}
|
||||
&.more {
|
||||
padding-top: 23rem;
|
||||
}
|
||||
&.more.status {
|
||||
padding-top: 26rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -215,6 +215,18 @@ describe("<SelectPlants />", () => {
|
|||
{ payload: undefined, type: Actions.SELECT_POINT });
|
||||
});
|
||||
|
||||
it("toggles more", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<SelectPlants>(<SelectPlants {...p} />);
|
||||
expect(wrapper.state().more).toEqual(false);
|
||||
expect(wrapper.find(".select-more").props().hidden).toBeTruthy();
|
||||
expect(wrapper.html()).not.toContain(" more status");
|
||||
wrapper.find(".more").simulate("click");
|
||||
expect(wrapper.state().more).toEqual(true);
|
||||
expect(wrapper.find(".select-more").props().hidden).toBeFalsy();
|
||||
expect(wrapper.html()).toContain(" more status");
|
||||
});
|
||||
|
||||
it("selects group items", () => {
|
||||
const p = fakeProps();
|
||||
p.selected = undefined;
|
||||
|
@ -236,7 +248,7 @@ describe("<SelectPlants />", () => {
|
|||
expect(wrapper.state().group_id).toEqual(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SET_SELECTION_POINT_TYPE,
|
||||
payload: POINTER_TYPES,
|
||||
payload: ["Plant"],
|
||||
});
|
||||
expect(p.dispatch).toHaveBeenLastCalledWith({
|
||||
type: Actions.SELECT_POINT,
|
||||
|
@ -265,6 +277,30 @@ describe("<SelectPlants />", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("selects selection type without criteria", () => {
|
||||
const p = fakeProps();
|
||||
const group = fakePointGroup();
|
||||
group.body.id = 1;
|
||||
group.body.criteria.string_eq = {};
|
||||
const plant = fakePlant();
|
||||
plant.body.id = 1;
|
||||
const weed = fakeWeed();
|
||||
weed.body.id = 2;
|
||||
group.body.point_ids = [1, 2];
|
||||
p.groups = [group];
|
||||
const dispatch = jest.fn();
|
||||
p.dispatch = mockDispatch(dispatch);
|
||||
const wrapper = mount<SelectPlants>(<SelectPlants {...p} />);
|
||||
const actionsWrapper = shallow(wrapper.instance().ActionButtons());
|
||||
actionsWrapper.find("FBSelect").at(1).simulate("change", {
|
||||
label: "", value: 1
|
||||
});
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: Actions.SET_SELECTION_POINT_TYPE,
|
||||
payload: POINTER_TYPES,
|
||||
});
|
||||
});
|
||||
|
||||
const DELETE_BTN_INDEX = 4;
|
||||
|
||||
it("confirms deletion of selected plants", () => {
|
||||
|
|
|
@ -25,7 +25,8 @@ import {
|
|||
} from "farmbot";
|
||||
import { UUID } from "../../resources/interfaces";
|
||||
import {
|
||||
selectAllActivePoints, selectAllToolSlotPointers, selectAllTools, selectAllPointGroups,
|
||||
selectAllActivePoints, selectAllToolSlotPointers, selectAllTools,
|
||||
selectAllPointGroups,
|
||||
} from "../../resources/selectors";
|
||||
import { PointInventoryItem } from "../points/point_inventory_item";
|
||||
import { ToolSlotInventoryItem } from "../tools";
|
||||
|
@ -108,11 +109,12 @@ export interface SelectPlantsProps {
|
|||
|
||||
interface SelectPlantsState {
|
||||
group_id: number | undefined;
|
||||
more: boolean;
|
||||
}
|
||||
|
||||
export class RawSelectPlants
|
||||
extends React.Component<SelectPlantsProps, SelectPlantsState> {
|
||||
state: SelectPlantsState = { group_id: undefined };
|
||||
state: SelectPlantsState = { group_id: undefined, more: false };
|
||||
|
||||
componentDidMount() {
|
||||
const { dispatch, selected } = this.props;
|
||||
|
@ -163,16 +165,24 @@ export class RawSelectPlants
|
|||
this.setState({ group_id });
|
||||
const group = this.props.groups
|
||||
.filter(pg => pg.body.id == group_id)[0];
|
||||
const pointUuids = pointsSelectedByGroup(group, this.props.allPoints)
|
||||
.map(p => p.uuid);
|
||||
const points = pointsSelectedByGroup(group, this.props.allPoints);
|
||||
const pointUuids = points.map(p => p.uuid);
|
||||
const pointerTypes =
|
||||
group.body.criteria.string_eq.pointer_type as PointType[] | undefined;
|
||||
this.props.dispatch(setSelectionPointType(pointerTypes || POINTER_TYPES));
|
||||
const uniqPointTypes = uniq(points.map(p => p.body.pointer_type));
|
||||
const pointTypes =
|
||||
uniqPointTypes.length == 1 ? [uniqPointTypes[0]] : undefined;
|
||||
this.props.dispatch(setSelectionPointType(
|
||||
pointerTypes || pointTypes || POINTER_TYPES));
|
||||
this.props.dispatch(selectPoint(pointUuids));
|
||||
}
|
||||
|
||||
ActionButtons = () =>
|
||||
<div className="panel-action-buttons">
|
||||
<div className={["panel-action-buttons",
|
||||
this.state.more ? "more" : "",
|
||||
["Plant", "Weed"].includes(this.selectionPointType) ? "status" : "",
|
||||
].join(" ")}>
|
||||
<label>{t("selection type")}</label>
|
||||
<FBSelect key={this.selectionPointType}
|
||||
list={POINTER_TYPE_LIST()}
|
||||
selectedItem={POINTER_TYPE_DDI_LOOKUP()[this.selectionPointType]}
|
||||
|
@ -185,22 +195,36 @@ export class RawSelectPlants
|
|||
<div className="button-row">
|
||||
<button className="fb-button gray"
|
||||
title={t("Select none")}
|
||||
onClick={() => this.props.dispatch(selectPoint(undefined))}>
|
||||
onClick={() => {
|
||||
this.setState({ group_id: undefined });
|
||||
this.props.dispatch(selectPoint(undefined));
|
||||
}}>
|
||||
{t("Select none")}
|
||||
</button>
|
||||
<button className="fb-button gray"
|
||||
title={t("Select all")}
|
||||
onClick={() => this.props.dispatch(selectPoint(this.allPointUuids))}>
|
||||
onClick={() => {
|
||||
this.setState({ group_id: undefined });
|
||||
this.props.dispatch(selectPoint(this.allPointUuids));
|
||||
}}>
|
||||
{t("Select all")}
|
||||
</button>
|
||||
<label>{t("select all in group")}</label>
|
||||
<FBSelect key={this.selectionPointType}
|
||||
list={Object.values(this.groupDDILookup)}
|
||||
selectedItem={this.state.group_id
|
||||
? this.groupDDILookup[this.state.group_id]
|
||||
: undefined}
|
||||
customNullLabel={t("Select a group")}
|
||||
onChange={this.selectGroup} />
|
||||
<div className="more"
|
||||
onClick={() => this.setState({ more: !this.state.more })}>
|
||||
<p>{this.state.more ? t("Less") : t("More")}</p>
|
||||
<i className={`fa fa-caret-${this.state.more ? "up" : "down"}`}
|
||||
title={this.state.more ? t("less") : t("more")} />
|
||||
</div>
|
||||
<div className={"select-more"} hidden={!this.state.more}>
|
||||
<label>{t("select all in group")}</label>
|
||||
<FBSelect key={`${this.selectionPointType}-${this.state.group_id}`}
|
||||
list={Object.values(this.groupDDILookup)}
|
||||
selectedItem={this.state.group_id
|
||||
? this.groupDDILookup[this.state.group_id]
|
||||
: undefined}
|
||||
customNullLabel={t("Select a group")}
|
||||
onChange={this.selectGroup} />
|
||||
</div>
|
||||
</div>
|
||||
<label>{t("SELECTION ACTIONS")}</label>
|
||||
<div className="button-row">
|
||||
|
@ -273,7 +297,11 @@ export class RawSelectPlants
|
|||
description={Content.BOX_SELECT_DESCRIPTION} />
|
||||
<this.ActionButtons />
|
||||
|
||||
<DesignerPanelContent panelName={"plant-selection"}>
|
||||
<DesignerPanelContent panelName={"plant-selection"}
|
||||
className={[
|
||||
this.state.more ? "more" : "",
|
||||
["Plant", "Weed"].includes(this.selectionPointType) ? "status" : "",
|
||||
].join(" ")}>
|
||||
{this.selectedPointData.map(p => {
|
||||
if (p.kind == "PlantTemplate" || p.body.pointer_type == "Plant") {
|
||||
return <PlantInventoryItem
|
||||
|
|
|
@ -2,9 +2,11 @@ import { FBToast } from "../fb_toast";
|
|||
|
||||
describe("FBToast", () => {
|
||||
let count = 0;
|
||||
const newToast = (): [FBToast, HTMLDivElement] => {
|
||||
const newToast = (idPrefix = ""): [FBToast, HTMLDivElement] => {
|
||||
const parent = document.createElement("div");
|
||||
const child = new FBToast(parent, "title", "message" + (count++), "red");
|
||||
const child =
|
||||
new FBToast(parent, "title", "message" + (count++), "red", idPrefix);
|
||||
parent.appendChild(child.toastEl);
|
||||
return [child, parent];
|
||||
};
|
||||
|
||||
|
@ -90,6 +92,30 @@ describe("FBToast", () => {
|
|||
i.detach();
|
||||
expect(FBToast.everyMessage[message]).toBeFalsy();
|
||||
expect(p.removeChild).toHaveBeenCalledWith(i.toastEl);
|
||||
expect(i.isAttached).toBeFalsy();
|
||||
});
|
||||
|
||||
it("doesn't detach from the DOM", () => {
|
||||
const [i, p] = newToast();
|
||||
p.innerHTML = "";
|
||||
const { message } = i;
|
||||
FBToast.everyMessage[message] = true;
|
||||
p.removeChild = jest.fn();
|
||||
i.isAttached = true;
|
||||
i.detach();
|
||||
expect(FBToast.everyMessage[message]).toBeFalsy();
|
||||
expect(p.removeChild).not.toHaveBeenCalled();
|
||||
expect(i.isAttached).toBeTruthy();
|
||||
});
|
||||
|
||||
it("sets id", () => {
|
||||
const toast = newToast("id-prefix")[0];
|
||||
expect(toast.toastEl.id).toEqual(expect.stringMatching("^id-prefix-toast-"));
|
||||
});
|
||||
|
||||
it("doesn't set id", () => {
|
||||
const toast = newToast()[0];
|
||||
expect(toast.toastEl.id).toEqual("");
|
||||
});
|
||||
|
||||
it("does polling", () => {
|
||||
|
|
|
@ -20,20 +20,34 @@ describe("toast internal support files", () => {
|
|||
container.className = "toast-container";
|
||||
document.body.appendChild(container);
|
||||
|
||||
createToastOnce(msg, "bar", "baz", fallback);
|
||||
createToastOnce(msg, "bar", "baz", "id-prefix", fallback);
|
||||
|
||||
expect(FBToast.everyMessage[msg]).toBe(true);
|
||||
expect(fallback).not.toHaveBeenCalled();
|
||||
expect(mockRun).toHaveBeenCalled();
|
||||
|
||||
createToastOnce(msg, "bar", "baz", fallback);
|
||||
createToastOnce(msg, "bar", "baz", "id-prefix", fallback);
|
||||
|
||||
expect(fallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses default fallback logger", () => {
|
||||
document.body.innerHTML = "";
|
||||
console.warn = jest.fn();
|
||||
const container = document.createElement("DIV");
|
||||
container.className = "toast-container";
|
||||
document.body.appendChild(container);
|
||||
const msg = "foo";
|
||||
delete FBToast.everyMessage[msg];
|
||||
createToastOnce(msg, "bar", "baz", "");
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
expect(mockRun).toHaveBeenCalled();
|
||||
createToastOnce(msg, "bar", "baz", "");
|
||||
expect(console.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("crashes if you don't attach .toast-container", () => {
|
||||
document.body.innerHTML = "";
|
||||
expect(() => createToast("x", "y", "z"))
|
||||
.toThrow();
|
||||
expect(() => createToast("x", "y", "z", "id-prefix")).toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,93 +14,113 @@ const {
|
|||
info,
|
||||
fun,
|
||||
init,
|
||||
removeToast,
|
||||
busy,
|
||||
}: typeof import("../toast") = jest.requireActual("../toast");
|
||||
|
||||
describe("toasts", () => {
|
||||
it("pops a warning() toast", () => {
|
||||
warning("test suite msg 1");
|
||||
expect(createToastOnce).toHaveBeenCalledWith("test suite msg 1",
|
||||
"Warning",
|
||||
"orange",
|
||||
console.warn);
|
||||
expect(createToastOnce).toHaveBeenCalledWith(
|
||||
"test suite msg 1", "Warning", "orange", "", console.warn);
|
||||
});
|
||||
|
||||
it("pops a warning() toast with different title and color", () => {
|
||||
warning("test suite msg", "new title", "purple");
|
||||
expect(createToastOnce)
|
||||
.toHaveBeenCalledWith("test suite msg", "new title", "purple",
|
||||
console.warn);
|
||||
warning("test suite msg", "new title", "purple", "id-prefix");
|
||||
expect(createToastOnce).toHaveBeenCalledWith(
|
||||
"test suite msg", "new title", "purple", "id-prefix", console.warn);
|
||||
});
|
||||
|
||||
it("pops a error() toast", () => {
|
||||
error("test suite msg 2");
|
||||
expect(createToastOnce).toHaveBeenCalledWith("test suite msg 2",
|
||||
"Error",
|
||||
"red",
|
||||
console.error);
|
||||
expect(createToastOnce).toHaveBeenCalledWith(
|
||||
"test suite msg 2", "Error", "red", "", console.error);
|
||||
});
|
||||
|
||||
it("pops a error() toast with different title and color", () => {
|
||||
error("test suite msg", "new title", "purple");
|
||||
expect(createToastOnce)
|
||||
.toHaveBeenCalledWith("test suite msg", "new title", "purple",
|
||||
console.error);
|
||||
error("test suite msg", "new title", "purple", "id-prefix");
|
||||
expect(createToastOnce).toHaveBeenCalledWith(
|
||||
"test suite msg", "new title", "purple", "id-prefix", console.error);
|
||||
});
|
||||
|
||||
it("pops a success() toast", () => {
|
||||
success("test suite msg");
|
||||
expect(createToast)
|
||||
.toHaveBeenCalledWith("test suite msg", "Success", "green");
|
||||
expect(createToast).toHaveBeenCalledWith(
|
||||
"test suite msg", "Success", "green", "");
|
||||
});
|
||||
|
||||
it("pops a success() toast with different title and color", () => {
|
||||
success("test suite msg", "new title", "purple");
|
||||
expect(createToast)
|
||||
.toHaveBeenCalledWith("test suite msg", "new title", "purple");
|
||||
success("test suite msg", "new title", "purple", "id-prefix");
|
||||
expect(createToast).toHaveBeenCalledWith(
|
||||
"test suite msg", "new title", "purple", "id-prefix");
|
||||
});
|
||||
|
||||
it("pops a info() toast", () => {
|
||||
info("test suite msg");
|
||||
expect(createToast)
|
||||
.toHaveBeenCalledWith("test suite msg", "FYI", "blue");
|
||||
expect(createToast).toHaveBeenCalledWith(
|
||||
"test suite msg", "FYI", "blue", "");
|
||||
});
|
||||
|
||||
it("pops a info() toast with different title and color", () => {
|
||||
info("test suite msg", "new title", "purple");
|
||||
expect(createToast)
|
||||
.toHaveBeenCalledWith("test suite msg", "new title", "purple");
|
||||
info("test suite msg", "new title", "purple", "id-prefix");
|
||||
expect(createToast).toHaveBeenCalledWith(
|
||||
"test suite msg", "new title", "purple", "id-prefix");
|
||||
});
|
||||
|
||||
it("pops a busy() toast", () => {
|
||||
busy("test suite msg");
|
||||
expect(createToast)
|
||||
.toHaveBeenCalledWith("test suite msg", "Busy", "yellow");
|
||||
expect(createToast).toHaveBeenCalledWith(
|
||||
"test suite msg", "Busy", "yellow", "");
|
||||
});
|
||||
|
||||
it("pops a busy() toast with different title and color", () => {
|
||||
busy("test suite msg", "new title", "purple");
|
||||
expect(createToast)
|
||||
.toHaveBeenCalledWith("test suite msg", "new title", "purple");
|
||||
busy("test suite msg", "new title", "purple", "id-prefix");
|
||||
expect(createToast).toHaveBeenCalledWith(
|
||||
"test suite msg", "new title", "purple", "id-prefix");
|
||||
});
|
||||
|
||||
it("pops a fun() toast", () => {
|
||||
fun("test suite msg");
|
||||
expect(createToast)
|
||||
.toHaveBeenCalledWith("test suite msg", "Did you know?", "dark-blue");
|
||||
expect(createToast).toHaveBeenCalledWith(
|
||||
"test suite msg", "Did you know?", "dark-blue", "");
|
||||
});
|
||||
|
||||
it("pops a fun() toast with different title and color", () => {
|
||||
fun("test suite msg", "new title", "purple");
|
||||
expect(createToast)
|
||||
.toHaveBeenCalledWith("test suite msg", "new title", "purple");
|
||||
fun("test suite msg", "new title", "purple", "id-prefix");
|
||||
expect(createToast).toHaveBeenCalledWith(
|
||||
"test suite msg", "new title", "purple", "id-prefix");
|
||||
});
|
||||
|
||||
const getToastContainerCount = () =>
|
||||
Object.values(document.querySelectorAll(".toast-container")).length;
|
||||
|
||||
const getToastCount = () =>
|
||||
document.querySelector(".toast-container")?.childElementCount;
|
||||
|
||||
it("adds the appropriate div to the DOM", () => {
|
||||
const count1 = document.querySelectorAll(".toast-container").item.length;
|
||||
expect(count1).toEqual(1);
|
||||
document.body.innerHTML = "";
|
||||
expect(getToastContainerCount()).toEqual(0);
|
||||
init();
|
||||
const count2 = document.querySelectorAll(".toast-container").item.length;
|
||||
expect(count2).toEqual(1);
|
||||
expect(getToastContainerCount()).toEqual(1);
|
||||
});
|
||||
|
||||
it("removes a toast message", () => {
|
||||
document.body.innerHTML = "";
|
||||
init();
|
||||
expect(getToastCount()).toEqual(0);
|
||||
const toast = document.createElement("div");
|
||||
toast.id = "id-prefix-123";
|
||||
document.querySelector(".toast-container")?.appendChild(toast);
|
||||
expect(getToastCount()).toEqual(1);
|
||||
removeToast("id-prefix");
|
||||
expect(getToastCount()).toEqual(0);
|
||||
});
|
||||
|
||||
it("doesn't remove a toast message: parent missing", () => {
|
||||
document.body.innerHTML = "";
|
||||
expect(getToastContainerCount()).toEqual(0);
|
||||
console.error = jest.fn();
|
||||
removeToast("id-prefix");
|
||||
expect(console.error).toHaveBeenCalledWith("toast-container is null.");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { uuid } from "farmbot";
|
||||
|
||||
/** This is a [surprisingly reliable] legacy component.
|
||||
* TODO: Convert this to React. */
|
||||
export class FBToast {
|
||||
|
@ -29,7 +31,10 @@ export class FBToast {
|
|||
constructor(public parent: Element,
|
||||
title: string,
|
||||
raw_message: string,
|
||||
color: string) {
|
||||
color: string,
|
||||
idPrefix: string) {
|
||||
|
||||
idPrefix && (this.toastEl.id = `${idPrefix}-toast-${uuid()}`);
|
||||
|
||||
this.message = raw_message.replace(/\s+/g, " ");
|
||||
/** Fill contents. */
|
||||
|
@ -84,7 +89,7 @@ export class FBToast {
|
|||
detach = () => {
|
||||
clearInterval(this.intervalId);
|
||||
delete FBToast.everyMessage[this.message];
|
||||
if (this.isAttached) {
|
||||
if (this.isAttached && this.parent.contains(this.toastEl)) {
|
||||
this.parent.removeChild(this.toastEl);
|
||||
this.isAttached = false;
|
||||
}
|
||||
|
|
|
@ -4,45 +4,70 @@ import { t } from "../i18next_wrapper";
|
|||
/**
|
||||
* Orange message with "Warning" as the default title.
|
||||
*/
|
||||
export const warning =
|
||||
(message: string, title = t("Warning"), color = "orange") => {
|
||||
createToastOnce(message, title, color, console.warn);
|
||||
};
|
||||
export const warning = (
|
||||
message: string,
|
||||
title = t("Warning"),
|
||||
color = "orange",
|
||||
idPrefix = "",
|
||||
) => {
|
||||
createToastOnce(message, title, color, idPrefix, console.warn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Red message with "Error" as the default title.
|
||||
*/
|
||||
export const error = (message: string, title = t("Error"), color = "red") => {
|
||||
createToastOnce(message, title, color, console.error);
|
||||
export const error = (
|
||||
message: string,
|
||||
title = t("Error"),
|
||||
color = "red",
|
||||
idPrefix = "",
|
||||
) => {
|
||||
createToastOnce(message, title, color, idPrefix, console.error);
|
||||
};
|
||||
|
||||
/**
|
||||
* Green message with "Success" as the default title.
|
||||
*/
|
||||
export const success =
|
||||
(message: string, title = t("Success"), color = "green") =>
|
||||
createToast(message, title, color);
|
||||
export const success = (
|
||||
message: string,
|
||||
title = t("Success"),
|
||||
color = "green",
|
||||
idPrefix = "",
|
||||
) =>
|
||||
createToast(message, title, color, idPrefix);
|
||||
|
||||
/**
|
||||
* Blue message with "FYI" as the default title.
|
||||
*/
|
||||
export const info =
|
||||
(message: string, title = t("FYI"), color = "blue") =>
|
||||
createToast(message, title, color);
|
||||
export const info = (
|
||||
message: string,
|
||||
title = t("FYI"),
|
||||
color = "blue",
|
||||
idPrefix = "",
|
||||
) =>
|
||||
createToast(message, title, color, idPrefix);
|
||||
|
||||
/**
|
||||
* Yellow message with "Busy" as the default title.
|
||||
*/
|
||||
export const busy =
|
||||
(message: string, title = t("Busy"), color = "yellow") =>
|
||||
createToast(message, title, color);
|
||||
export const busy = (
|
||||
message: string,
|
||||
title = t("Busy"),
|
||||
color = "yellow",
|
||||
idPrefix = "",
|
||||
) =>
|
||||
createToast(message, title, color, idPrefix);
|
||||
|
||||
/**
|
||||
* Dark blue message with "Did you know?" as the default title.
|
||||
*/
|
||||
export const fun =
|
||||
(message: string, title = t("Did you know?"), color = "dark-blue") =>
|
||||
createToast(message, title, color);
|
||||
export const fun = (
|
||||
message: string,
|
||||
title = t("Did you know?"),
|
||||
color = "dark-blue",
|
||||
idPrefix = "",
|
||||
) =>
|
||||
createToast(message, title, color, idPrefix);
|
||||
|
||||
/**
|
||||
* Adds a hidden container div for holding toast messages.
|
||||
|
@ -52,3 +77,14 @@ export const init = () => {
|
|||
toastContainer.classList.add("toast-container");
|
||||
document.body.appendChild(toastContainer);
|
||||
};
|
||||
|
||||
/** Remove all toast messages that match the provided id prefix. */
|
||||
export const removeToast = (idPrefix: string) => {
|
||||
const parent = document.querySelector(".toast-container");
|
||||
const toasts = document.querySelectorAll(`[id^="${idPrefix}-"]`);
|
||||
if (parent) {
|
||||
Object.values(toasts).map(toast => parent.removeChild(toast));
|
||||
} else {
|
||||
console.error("toast-container is null.");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,7 +3,9 @@ import { FBToast } from "./fb_toast";
|
|||
/**
|
||||
* The function responsible for attaching the messages to the container.
|
||||
*/
|
||||
export const createToast = (message: string, title: string, color: string) => {
|
||||
export const createToast = (
|
||||
message: string, title: string, color: string, idPrefix: string,
|
||||
) => {
|
||||
|
||||
/**
|
||||
* Container element for all of the messages created from init().
|
||||
|
@ -17,18 +19,20 @@ export const createToast = (message: string, title: string, color: string) => {
|
|||
/**
|
||||
* Create elements.
|
||||
*/
|
||||
const t = new FBToast(parent, title, message, color);
|
||||
const t = new FBToast(parent, title, message, color, idPrefix);
|
||||
t.run();
|
||||
};
|
||||
|
||||
export const createToastOnce = (message: string,
|
||||
title: string,
|
||||
color: string,
|
||||
fallbackLogger = console.warn) => {
|
||||
idPrefix: string,
|
||||
fallbackLogger = console.warn,
|
||||
) => {
|
||||
if (FBToast.everyMessage[message]) {
|
||||
fallbackLogger(message);
|
||||
} else {
|
||||
createToast(message, title, color);
|
||||
createToast(message, title, color, idPrefix);
|
||||
FBToast.everyMessage[message] = true;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue