refactor using new ts features

pull/1652/head
gabrielburnworth 2020-01-03 12:04:45 -08:00
parent a32d9f025b
commit a38e7b6b91
52 changed files with 77 additions and 107 deletions

View File

@ -7,11 +7,11 @@ export function clickButton(
position: number,
text: string,
options?: { partial_match?: boolean, button_tag?: string }) {
const btnTag = options && options.button_tag ? options.button_tag : "button";
const btnTag = options?.button_tag ? options.button_tag : "button";
const button = wrapper.find(btnTag).at(position);
const expectedText = text.toLowerCase();
const actualText = button.text().toLowerCase();
options && options.partial_match
options?.partial_match
? expect(actualText).toContain(expectedText)
: expect(actualText).toEqual(expectedText);
button.simulate("click");

View File

@ -32,7 +32,7 @@ test("buildResourceIndex - add a FarmEvent", () => {
const key = Object.keys(db.index.byKind.FarmEvent)[0];
const fe = db.index.references[key];
expect(fe).toBeTruthy();
if (fe && fe.kind === "FarmEvent") {
if (fe?.kind === "FarmEvent") {
const { body } = fe;
expect(body).toEqual(STUB_RESOURCE.body);
} else {

View File

@ -1,12 +1,11 @@
import * as React from "react";
import { AxisInputBoxProps } from "./interfaces";
import { Col, BlurableInput } from "../ui/index";
import { isUndefined } from "lodash";
export const AxisInputBox = ({ onChange, value, axis }: AxisInputBoxProps) => {
return <Col xs={3}>
<BlurableInput
value={(isUndefined(value) ? "" : value)}
value={value ?? ""}
type="number"
allowEmpty={true}
onCommit={e => {

View File

@ -36,8 +36,8 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
</Col>
<Col xs={2} className={"centered-button-div"}>
<ToggleButton
grayscale={grayscale && grayscale.x}
disabled={disable && disable.x}
grayscale={grayscale?.x}
disabled={disable?.x}
dim={!xParam.consistent}
toggleValue={xParam.value}
toggleAction={() =>
@ -45,8 +45,8 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
</Col>
<Col xs={2} className={"centered-button-div"}>
<ToggleButton
grayscale={grayscale && grayscale.y}
disabled={disable && disable.y}
grayscale={grayscale?.y}
disabled={disable?.y}
dim={!yParam.consistent}
toggleValue={yParam.value}
toggleAction={() =>
@ -54,8 +54,8 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
</Col>
<Col xs={2} className={"centered-button-div"}>
<ToggleButton
grayscale={grayscale && grayscale.z}
disabled={disable && disable.z}
grayscale={grayscale?.z}
disabled={disable?.z}
dim={!zParam.consistent}
toggleValue={zParam.value}
toggleAction={() =>

View File

@ -115,7 +115,7 @@ describe("<PinBindingInputGroup/>", () => {
const p = fakeProps();
const key = Object.keys(p.resources.byKind.Sequence)[0];
const s = p.resources.references[key];
const id = s && s.body.id;
const id = s?.body.id;
const wrapper = mount<PinBindingInputGroup>(<PinBindingInputGroup {...p} />);
expect(wrapper.instance().state.sequenceIdInput).toEqual(undefined);
wrapper.instance().setSequenceIdInput({ label: "label", value: "" + id });

View File

@ -79,8 +79,8 @@ export const mcuParamValidator =
(ok: () => void, no?: (message: string) => void): void => {
const validator = edgeCases[key];
const result = validator && validator(key, val, state);
if (result && result.outcome === "NO") {
return (no && no(result.errorMessage));
if (result?.outcome === "NO") {
return (no?.(result.errorMessage));
} else {
return ok();
}

View File

@ -26,7 +26,7 @@ describe("draggableReducer", () => {
const dt = nextState.dataTransfer;
expect(Object.keys(dt)).toContain(payload.uuid);
const entry = dt[payload.uuid];
expect(entry && entry.uuid).toEqual(payload.uuid);
expect(entry?.uuid).toEqual(payload.uuid);
});
it("drops a step", () => {

View File

@ -56,7 +56,7 @@ export const DesignerPanelHeader = (props: DesignerPanelHeaderProps) => {
title={t("go back") + backToText(props.backTo)}
onClick={() => {
props.backTo ? routeHistory.push(props.backTo) : history.back();
props.onBack && props.onBack();
props.onBack?.();
}} />
{props.title &&
<span className={`title ${textColor}-text`}>

View File

@ -16,10 +16,10 @@ export function occurrence(
CalendarOccurrence {
const normalHeading = fe.executable.name || fe.executable_type;
const heading = () => {
if (modifiers && modifiers.empty) {
if (modifiers?.empty) {
return "*Empty*";
}
if (modifiers && modifiers.numHidden) {
if (modifiers?.numHidden) {
return `+ ${modifiers.numHidden} more: ` + normalHeading;
}
return normalHeading;

View File

@ -36,7 +36,7 @@ const addOrRemoveFromGroup =
const group = fetchGroupFromUrl(resources);
const point =
resources.references[clickedPlantUuid] as TaggedPoint | undefined;
if (group && point && point.body.id) {
if (group && point?.body.id) {
type Body = (typeof group)["body"];
const nextGroup: Body = ({
...group.body,
@ -54,7 +54,7 @@ const addOrRemoveFromSelection =
(clickedPlantUuid: UUID, selectedPlants: UUID[] | undefined) => {
const nextSelected =
(selectedPlants || []).filter(uuid => uuid !== clickedPlantUuid);
if (!(selectedPlants && selectedPlants.includes(clickedPlantUuid))) {
if (!(selectedPlants?.includes(clickedPlantUuid))) {
nextSelected.push(clickedPlantUuid);
}
return selectPlant(nextSelected);

View File

@ -21,7 +21,7 @@ function getNewTrailArray(update: TrailRecord, watering: boolean): TrailRecord[]
const arr: TrailRecord[] = JSON.parse(get(sessionStorage, key, "[]"));
if (arr.length > (trailLength - 1)) { arr.shift(); } // max length reached
const last = arr[arr.length - 1]; // most recent item in array
if (update && update.coord &&
if (update?.coord &&
(!last || !isEqual(last.coord, update.coord))) { // coordinate comparison
arr.push(update); // unique addition
} else { // nothing new to add, increase water circle size if watering

View File

@ -7,7 +7,7 @@ import { TaggedPlant } from "../map/interfaces";
import { DesignerPanel, DesignerPanelHeader } from "../designer_panel";
import { t } from "../../i18next_wrapper";
import { EditPlantInfoProps, PlantOptions } from "../interfaces";
import { isString, isUndefined } from "lodash";
import { isString } from "lodash";
import { history, getPathArray } from "../../history";
import { destroy, edit, save } from "../../api/crud";
import { BooleanSetting } from "../../session_keys";
@ -20,7 +20,7 @@ export class RawPlantInfo extends React.Component<EditPlantInfoProps, {}> {
get confirmDelete() {
const confirmSetting = this.props.getConfigValue(
BooleanSetting.confirm_plant_deletion);
return isUndefined(confirmSetting) ? true : confirmSetting;
return confirmSetting ?? true;
}
destroy = (plantUUID: string) => {

View File

@ -51,7 +51,7 @@ describe("", () => {
const sort = (sortType: PointGroupSortType): string[] => {
const array = SORT_OPTIONS[sortType](plants as TaggedPlant[]);
return array.map(x => x && x.body && (x.body.name || "NA"));
return array.map(x => x?.body?.name || "NA");
};
it("sorts randomly", () => {

View File

@ -66,7 +66,7 @@ export const mapStateToProps = (props: Everything): EditGardenProps => {
const savedGarden = findSavedGardenByUrl(props.resources.index);
return {
savedGarden,
gardenIsOpen: !!(savedGarden && savedGarden.uuid === openedSavedGarden),
gardenIsOpen: !!(savedGarden?.uuid === openedSavedGarden),
dispatch: props.dispatch,
plantPointerCount: selectAllPlantPointers(props.resources.index).length,
};

View File

@ -71,7 +71,7 @@ const Setting = (props: SettingProps) => {
toggleValue={props.invert ? !value : value}
toggleAction={() => {
props.dispatch(setWebAppConfigValue(setting, !value));
callback && callback();
callback?.();
}}
title={`${t("toggle")} ${title}`}
customText={{ textFalse: t("off"), textTrue: t("on") }} />}

View File

@ -100,7 +100,7 @@ export class RawAddToolSlot
: "initializing"}
<SaveBtn onClick={this.save} status={SpecialStatus.DIRTY} />
</DesignerPanelContent>
</DesignerPanel >;
</DesignerPanel>;
}
}

View File

@ -24,7 +24,7 @@ export function FarmwareConfigMenu(props: FarmwareConfigMenuProps) {
className="fb-button gray fa fa-download"
onClick={() => {
const p = getDevice().installFirstPartyFarmware();
p && p.catch(commandErr("Farmware installation"));
p?.catch(commandErr("Farmware installation"));
}}
disabled={props.firstPartyFwsInstalled} />
</fieldset>

View File

@ -108,6 +108,6 @@ export function FarmwareForm(props: FarmwareFormProps): JSX.Element {
/** Determine if a Farmware has requested inputs. */
export function needsFarmwareForm(farmware: FarmwareManifestInfo): Boolean {
const needsWidget = farmware.config && farmware.config.length > 0;
const needsWidget = farmware.config?.length > 0;
return needsWidget;
}

View File

@ -57,7 +57,7 @@ const PendingInstallNameError =
}): JSX.Element => {
const installation: TaggedFarmwareInstallation | undefined =
installations.filter(x => x.body.url === url)[0];
const packageError = installation && installation.body.package_error;
const packageError = installation?.body.package_error;
return (url && installation && packageError)
? <div className="error-with-button">
<label>{t("Could not fetch package name")}</label>

View File

@ -116,7 +116,7 @@ export class Photos extends React.Component<PhotosProps, {}> {
deletePhoto = () => {
const img = this.props.currentImage || this.props.images[0];
if (img && img.uuid) {
if (img?.uuid) {
this.props.dispatch(destroy(img.uuid))
.then(() => success(t("Image Deleted.")))
.catch(() => error(t("Could not delete image.")));

View File

@ -61,7 +61,7 @@ export class ImageWorkspace extends React.Component<ImageWorkspaceProps, {}> {
maybeProcessPhoto = () => {
const img = this.props.currentImage || this.props.images[0];
if (img && img.body.id) {
if (img?.body.id) {
this.props.onProcessPhoto(img.body.id);
}
};

View File

@ -80,7 +80,7 @@ export const FolderListItem = (props: FolderItemProps) => {
onMouseUp={() => props.toggleSequenceMove(sequence.uuid)} />
</div>
</li>
</StepDragger >;
</StepDragger>;
};
const ToggleFolderBtn = (props: ToggleFolderBtnProps) => {

View File

@ -1,5 +1,4 @@
import { TaggedResource, TaggedSequence } from "farmbot";
import { RootFolderNode, FolderUnion } from "./constants";
export interface FolderSearchProps {

View File

@ -18,7 +18,7 @@ export const clickHandler =
/** BEGIN LEGACY SHIMS */
const { onClick, to } = props;
navigate(maybeStripLegacyUrl(to));
onClick && onClick(e);
onClick?.(e);
};
export class Link extends React.Component<LinkProps, {}> {

View File

@ -180,7 +180,7 @@ const FirmwareChoiceTable = () =>
export const changeFirmwareHardware = (dispatch: Function | undefined) =>
(ddi: DropDownItem) => {
if (isFwHardwareValue(ddi.value)) {
dispatch && dispatch(updateConfig({ firmware_hardware: ddi.value }));
dispatch?.(updateConfig({ firmware_hardware: ddi.value }));
}
};

View File

@ -11,7 +11,7 @@ export const computeEditorUrlFromState =
: resources.consumers.regimens.currentRegimen;
const r = resources.index.references[current || ""];
const base = `/app/${resource === "Sequence" ? "sequences" : "regimens"}/`;
if (r && r.kind == resource) {
if (r?.kind == resource) {
return base + urlFriendly(r.body.name);
} else {
return base;

View File

@ -59,7 +59,7 @@ export class NavBar extends React.Component<NavBarProps, Partial<NavBarState>> {
BooleanSetting.disable_emergency_unlock_confirmation)} />
AccountMenu = () => {
const hasName = this.props.user && this.props.user.body.name;
const hasName = this.props.user?.body.name;
const firstName = hasName ?
`${hasName.split(" ")[0].slice(0, 9)}` : `${t("Menu")}`;
return <div className="menu-popover">

View File

@ -43,10 +43,7 @@ const promiseCache: Dictionary<Promise<Readonly<OFCropAttrs>>> = {};
const cacheTheIcon = (slug: string) =>
(resp: AxiosResponse<OFCropResponse>): OFIcon => {
if (resp
&& resp.data
&& resp.data.data
&& resp.data.data.attributes) {
if (resp?.data?.data?.attributes) {
const icon = {
slug: resp.data.data.attributes.slug,
spread: resp.data.data.attributes.spread,

View File

@ -35,7 +35,7 @@ export function getMiddleware(env: EnvName) {
.map((mwc) => mwc.fn);
// tslint:disable-next-line:no-any
const wow = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
const dtCompose = wow && wow({
const dtCompose = wow?.({
actionsBlacklist: [
Actions.NETWORK_EDGE_CHANGE,
Actions.PING_NO,

View File

@ -12,10 +12,8 @@ const WEB_APP_CONFIG: ResourceName = "WebAppConfig";
* resources, downloading the filtered log list as required from the API. */
// tslint:disable-next-line:no-any
export const fn: Middleware = () => (dispatch) => (action: any) => {
const needsRefresh = action
&& action.payload
&& action.type === Actions.SAVE_RESOURCE_OK
&& action.payload.kind === WEB_APP_CONFIG;
const needsRefresh = action?.payload?.kind === WEB_APP_CONFIG
&& action.type === Actions.SAVE_RESOURCE_OK;
needsRefresh && throttledLogRefresh(dispatch);

View File

@ -23,16 +23,11 @@ const WEB_APP_CONFIG: ResourceName = "WebAppConfig";
// tslint:disable-next-line:no-any
const fn: Middleware = () => (dispatch) => (action: any) => {
const x: DeepPartial<SyncResponse<TaggedWebAppConfig>> = action;
if (x
&& x.type === Actions.RESOURCE_READY
&& x.payload
&& x.payload.body
if (x?.type === Actions.RESOURCE_READY
&& x.payload?.body
&& x.payload.kind === WEB_APP_CONFIG) {
const conf = arrayUnwrap(x.payload.body);
conf
&& conf.body
&& conf.body.disable_i18n
&& revertToEnglish();
conf?.body?.disable_i18n && revertToEnglish();
}
return dispatch(action);

View File

@ -12,10 +12,9 @@ export function dontStopThem() { }
const shouldStop =
(allResources: TaggedResource[], config: TaggedWebAppConfig | undefined) => {
const loggedIn = !!localStorage.getItem("session");
const discardUnsaved = config && config.body.discard_unsaved;
const discardUnsaved = config?.body.discard_unsaved;
const sequenceResources = allResources.filter(r => r.kind === "Sequence");
const discardUnsavedSequences =
config && config.body.discard_unsaved_sequences;
const discardUnsavedSequences = config?.body.discard_unsaved_sequences;
/**
* For the unsaved notification to show, a user must:
@ -59,6 +58,6 @@ export function registerSubscribers(store: Store) {
subscriptions.forEach(function (s) {
ENV_LIST.includes &&
ENV_LIST.includes(s.env) &&
store.subscribe(() => s.fn && s.fn(store.getState()));
store.subscribe(() => s.fn?.(store.getState()));
});
}

View File

@ -23,7 +23,7 @@ const fn: MW =
// tslint:disable-next-line:no-any
(action: any) => {
const fbos = getVersionFromState(store.getState());
window.Rollbar && window.Rollbar.configure({ payload: { fbos } });
window.Rollbar?.configure({ payload: { fbos } });
return dispatch(action);
};

View File

@ -17,7 +17,7 @@ const BAD_UUID = "WARNING: Not a sequence UUID.";
export class BulkScheduler extends React.Component<BulkEditorProps, {}> {
selected = (): DropDownItem => {
const s = this.props.selectedSequence;
return (s && s.body.id)
return (s?.body.id)
? { label: s.body.name, value: s.uuid }
: NULL_CHOICE;
};
@ -66,7 +66,7 @@ export class BulkScheduler extends React.Component<BulkEditorProps, {}> {
render() {
const { dispatch, weeks, sequences } = this.props;
const active = !!(sequences && sequences.length);
const active = !!(sequences?.length);
return <div className="bulk-scheduler-content">
<AddButton
active={active}

View File

@ -66,7 +66,7 @@ describe("<RegimenEditor />", () => {
p.dispatch = jest.fn(() => Promise.resolve());
const wrapper = mount(<RegimenEditor {...p} />);
clickButton(wrapper, 2, "delete");
const expectedUuid = p.current && p.current.uuid;
const expectedUuid = p.current?.uuid;
expect(destroy).toHaveBeenCalledWith(expectedUuid);
});
@ -74,7 +74,7 @@ describe("<RegimenEditor />", () => {
const p = fakeProps();
const wrapper = mount(<RegimenEditor {...p} />);
clickButton(wrapper, 0, "save", { partial_match: true });
const expectedUuid = p.current && p.current.uuid;
const expectedUuid = p.current?.uuid;
expect(save).toHaveBeenCalledWith(expectedUuid);
});
});

View File

@ -177,7 +177,7 @@ describe("maybeGetSequence", () => {
const i = buildResourceIndex([s]);
const result = Selector.maybeGetSequence(i.index, s.uuid);
expect(result).toBeTruthy();
result && expect(result.uuid).toBe(s.uuid);
expect(result?.uuid).toBe(s.uuid);
});
});

View File

@ -188,7 +188,7 @@ const reindexAllSequences = (i: ResourceIndex) => {
const mapper = reindexSequences(i);
betterCompact(Object.keys(i.byKind["Sequence"]).map(uuid => {
const resource = i.references[uuid];
return (resource && resource.kind == "Sequence") ? resource : undefined;
return (resource?.kind == "Sequence") ? resource : undefined;
})).map(mapper);
};
@ -283,7 +283,7 @@ const AFTER_HOOKS: IndexerHook = {
FbosConfig: (i) => {
const conf = getFbosConfig(i);
if (conf && conf.body.boot_sequence_id) {
if (conf?.body.boot_sequence_id) {
const { boot_sequence_id } = conf.body;
const tracker = i.inUse["Sequence.FbosConfig"];
const uuid = i.byKindAndId[joinKindAndId("Sequence", boot_sequence_id)];
@ -345,7 +345,7 @@ export const indexUpsert: IndexUpsert = (db, resources, strategy) => {
const { kind } = arrayUnwrap(resources);
// Clean up indexes (if needed)
const before = BEFORE_HOOKS[kind];
before && before(db, strategy);
before?.(db, strategy);
// Run indexers
ups.map(callback => {
@ -354,14 +354,14 @@ export const indexUpsert: IndexUpsert = (db, resources, strategy) => {
// Finalize indexing (if needed)
const after = AFTER_HOOKS[kind];
after && after(db, strategy);
after?.(db, strategy);
};
export function indexRemove(db: ResourceIndex, resource: TaggedResource) {
downs.map(callback => arrayWrap(resource).map(r => callback(r, db)));
// Finalize indexing (if needed)
const after = AFTER_HOOKS[resource.kind];
after && after(db, "ongoing");
after?.(db, "ongoing");
}
export const beforeEach = (state: RestResources,

View File

@ -77,7 +77,7 @@ export function findPointerByTypeAndId(index: ResourceIndex,
const uuid = "" + index.byKindAndId[pni];
const resource = index.references[uuid];
if (resource && resource.kind === "Point") {
if (resource?.kind === "Point") {
return resource;
} else {
// We might have a sequence dependency leak if this exception is ever
@ -204,7 +204,7 @@ export function maybeGetTimeSettings(index: ResourceIndex): TimeSettings {
export function maybeGetDevice(index: ResourceIndex): TaggedDevice | undefined {
const dev = index.references[Object.keys(index.byKind.Device)[0] || "nope"];
return (dev && dev.kind === "Device") ?
return (dev?.kind === "Device") ?
dev : undefined;
}
@ -227,7 +227,7 @@ export function maybeFetchUser(index: ResourceIndex):
if (user && sanityCheck(user) && list.length > 1) {
throw new Error("PROBLEM: Expected 1 user. Got: " + list.length);
}
if ((list.length === 1) && user && user.kind === "User") {
if ((list.length === 1) && user?.kind === "User") {
return user;
} else {
return undefined;

View File

@ -50,7 +50,7 @@ export const determineVector =
return ts ? ts.body : undefined;
case "identifier":
const variable = maybeFindVariable(node.args.label, resources, uuid);
return variable && variable.vector;
return variable?.vector;
}
return undefined;
};

View File

@ -127,7 +127,7 @@ describe("<SequenceEditorMiddleActive/>", () => {
p.dispatch = dispatch;
const wrapper = mount(<SequenceEditorMiddleActive {...p} />);
const props = wrapper.find("DropArea").props() as DropAreaProps;
props.callback && props.callback("key");
props.callback?.("key");
dispatch.mock.calls[0][0](() =>
({ value: 1, intent: "step_splice", draggerId: 2 }));
expect(splice).toHaveBeenCalledWith(expect.objectContaining({

View File

@ -148,7 +148,7 @@ const SequenceBtnGroup = ({
onClick={() => {
const confirm = getWebAppConfigValue(
BooleanSetting.confirm_sequence_deletion);
const force = isUndefined(confirm) ? false : !confirm;
const force = !(confirm ?? true);
dispatch(destroy(sequence.uuid, force))
.then(() => push("/app/sequences/"));
}}>

View File

@ -19,7 +19,7 @@ import {
export function mapStateToProps(props: Everything): Props {
const uuid = props.resources.consumers.sequences.current;
const sequence = uuid ? findSequence(props.resources.index, uuid) : undefined;
sequence && (sequence.body.body || []).map(x => getStepTag(x));
(sequence?.body.body || []).map(x => getStepTag(x));
const fwConfig = validFwConfig(getFirmwareConfig(props.resources.index));
const { mcu_params } = props.bot.hardware;

View File

@ -83,7 +83,7 @@ describe("<TileExecuteScript/>", () => {
it("shows special 1st-party Farmware name", () => {
const p = fakeProps();
(p.currentStep as ExecuteScript).args.label = "plant-detection";
p.farmwareData && p.farmwareData.farmwareNames.push("plant-detection");
p.farmwareData?.farmwareNames.push("plant-detection");
const wrapper = mount(<TileExecuteScript {...p} />);
expect(wrapper.find("label").length).toEqual(1);
expect(wrapper.text()).toContain("Weed Detector");

View File

@ -54,7 +54,7 @@ export function TileExecuteScript(props: StepParams) {
/** Configs (inputs) from Farmware manifest for <FarmwareInputs />. */
const currentFarmwareConfigDefaults = (fwName: string): FarmwareConfig[] => {
return farmwareData && farmwareData.farmwareConfigs[fwName]
return farmwareData?.farmwareConfigs[fwName]
? farmwareData.farmwareConfigs[fwName]
: [];
};

View File

@ -24,8 +24,8 @@ import {
const fakeResourceIndex = buildResourceIndex(FAKE_RESOURCES).index;
const fakeTaggedSequence = fakeResourceIndex
.references[Object.keys(fakeResourceIndex.byKind.Sequence)[0]] as TaggedSequence;
const fakeId = fakeTaggedSequence && fakeTaggedSequence.body.id || 0;
const fakeName = fakeTaggedSequence && fakeTaggedSequence.body.name || "";
const fakeId = fakeTaggedSequence.body.id || 0;
const fakeName = fakeTaggedSequence.body.name || "";
const expectedItem = { label: fakeName, value: fakeId };
function fakeProps(): IfParams {

View File

@ -30,7 +30,7 @@ export const newTaggedResource = <T extends TR>(kind: T["kind"],
return {
kind: kind as TaggedResource["kind"],
body: body as TaggedResource["body"],
uuid: generateUuid(body && body.id ? body.id : undefined, kind),
uuid: generateUuid(body?.id, kind),
specialStatus
} as T;
});

View File

@ -11,7 +11,7 @@ export class ToolBayList extends React.Component<ToolBayListProps, {}> {
ToolSlotListItem = (slot: TaggedToolSlotPointer, index: number) => {
const { getToolByToolSlotUUID } = this.props;
const tool = getToolByToolSlotUUID(slot.uuid);
const name = (tool && tool.body.name) || t("None");
const name = (tool?.body.name) || t("None");
return <Row key={slot.uuid}>
<Col xs={1}><label>{index + 1}</label></Col>
<Col xs={2}>{slot.body.gantry_mounted ? t("Gantry") : slot.body.x}</Col>

View File

@ -6,7 +6,7 @@ interface BackArrowProps {
export function BackArrow(props: BackArrowProps) {
const onClick = () => {
history.back();
props.onClick && props.onClick();
props.onClick?.();
};
return <a onClick={onClick} className="back-arrow">

View File

@ -47,7 +47,7 @@ export class BlurableInput extends React.Component<BIProps, Partial<BIState>> {
withinLimits = (options?: { toasts?: boolean }): boolean => {
const onError = (msg: string) => {
this.setState({ error: msg });
options && options.toasts && error(msg);
options?.toasts && error(msg);
};
if (this.props.type === "number") {

View File

@ -131,18 +131,6 @@ describe("util", () => {
});
});
describe("isUndefined()", () => {
it("undefined", () => {
const result = Util.isUndefined(undefined);
expect(result).toBeTruthy();
});
it("defined", () => {
const result = Util.isUndefined({});
expect(result).toBeFalsy();
});
});
describe("randomColor()", () => {
it("only picks valid colors", () => {
times(Util.colors.length * 1.5, () =>

View File

@ -28,7 +28,7 @@ export function prettyPrintApiErrors(err: AxiosErrorResponse) {
function safelyFetchErrors(err: AxiosErrorResponse): Dictionary<string> {
// In case the interpreter gives us an oddball error message.
if (err && err.response && err.response.data) {
if (err.response?.data) {
return err.response.data;
} else {
return {
@ -42,7 +42,7 @@ export function bail(message: string): never {
}
export const catchErrors = (error: Error) => {
if (window.Rollbar && window.Rollbar.error) {
if (window.Rollbar?.error) {
window.Rollbar.error(error);
} else {
throw error;

View File

@ -14,7 +14,6 @@ import {
sortBy,
merge,
isNumber,
isUndefined as lodashIsUndefined
} from "lodash";
import { t } from "../i18next_wrapper";
@ -88,10 +87,6 @@ export type CowardlyDictionary<T> = Dictionary<T | undefined>;
*/
export const NOT_SAVED = -1;
export function isUndefined(x: object | undefined): x is undefined {
return lodashIsUndefined(x);
}
/** Better than Array.proto.filter and compact() because the type checker
* knows what's going on.
*/
@ -190,7 +185,7 @@ export function validBotLocationData(
*/
export function validFwConfig(config: TaggedFirmwareConfig | undefined):
TaggedFirmwareConfig["body"] | undefined {
return (config && config.body.api_migrated)
return (config?.body.api_migrated)
? config.body
: undefined;
}
@ -200,7 +195,7 @@ export function validFwConfig(config: TaggedFirmwareConfig | undefined):
*/
export function validFbosConfig(
config: TaggedFbosConfig | undefined): TaggedFbosConfig["body"] | undefined {
return (config && config.body.api_migrated)
return (config?.body.api_migrated)
? config.body
: undefined;
}
@ -232,4 +227,4 @@ export const parseIntInput = (input: string): number => {
export const timeFormatString =
(timeSettings: TimeSettings | undefined): string =>
(timeSettings && timeSettings.hour24) ? "H:mm" : "h:mma";
(timeSettings?.hour24) ? "H:mm" : "h:mma";