mock cachedCrop in all tests
parent
341eaa4ac7
commit
1f36ddd57e
|
@ -1,3 +1,7 @@
|
|||
jest.mock("browser-speech", () => ({
|
||||
talk: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../open_farm/cached_crop", () => ({
|
||||
cachedCrop: jest.fn(() => Promise.resolve({ svg_icon: "icon" })),
|
||||
}));
|
||||
|
|
|
@ -13,7 +13,7 @@ import { MovePlantProps } from "../interfaces";
|
|||
import { fakePlant } from "../../__test_support__/fake_state/resources";
|
||||
import { edit } from "../../api/crud";
|
||||
import { Actions } from "../../constants";
|
||||
import { DEFAULT_ICON } from "../../open_farm/icons";
|
||||
import { DEFAULT_ICON, svgToUrl } from "../../open_farm/icons";
|
||||
import { history } from "../../history";
|
||||
|
||||
describe("movePlant", () => {
|
||||
|
@ -82,7 +82,7 @@ describe("setDragIcon()", () => {
|
|||
const e = { currentTarget: new Image(), dataTransfer: { setDragImage } };
|
||||
setDragIcon("icon")(e);
|
||||
const img = new Image();
|
||||
img.src = "data:image/svg+xml;utf8,icon";
|
||||
img.src = svgToUrl("icon");
|
||||
expect(setDragImage).toHaveBeenCalledWith(img, 0, 0);
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ jest.mock("../../../../../api/crud", () => ({
|
|||
}));
|
||||
|
||||
const mockSpreads: { [x: string]: number } = { mint: 100 };
|
||||
jest.mock("../../../../../open_farm/icons", () => ({
|
||||
jest.mock("../../../../../open_farm/cached_crop", () => ({
|
||||
cachedCrop: jest.fn(p => Promise.resolve({ spread: mockSpreads[p] })),
|
||||
}));
|
||||
|
||||
|
@ -24,7 +24,7 @@ import {
|
|||
} from "../plant_actions";
|
||||
import { fakePlant } from "../../../../../__test_support__/fake_state/resources";
|
||||
import { edit, save, initSave } from "../../../../../api/crud";
|
||||
import { cachedCrop } from "../../../../../open_farm/icons";
|
||||
import { cachedCrop } from "../../../../../open_farm/cached_crop";
|
||||
import {
|
||||
fakeMapTransformProps
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import * as React from "react";
|
||||
import { GardenPlantProps, GardenPlantState } from "../../interfaces";
|
||||
import { cachedCrop, DEFAULT_ICON, svgToUrl } from "../../../../open_farm/icons";
|
||||
import { DEFAULT_ICON, svgToUrl } from "../../../../open_farm/icons";
|
||||
import { round, transformXY } from "../../util";
|
||||
import { DragHelpers } from "../../active_plant/drag_helpers";
|
||||
import { Color } from "../../../../ui/index";
|
||||
import { Actions } from "../../../../constants";
|
||||
import { cachedCrop } from "../../../../open_farm/cached_crop";
|
||||
|
||||
export class GardenPlant extends
|
||||
React.Component<GardenPlantProps, Partial<GardenPlantState>> {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { error } from "farmbot-toastr";
|
||||
import { Content } from "../../../../constants";
|
||||
import { initSave, edit, save } from "../../../../api/crud";
|
||||
|
@ -14,7 +13,7 @@ import { getPathArray } from "../../../../history";
|
|||
import { findBySlug } from "../../../search_selectors";
|
||||
import { transformXY, round } from "../../util";
|
||||
import { movePlant } from "../../../actions";
|
||||
import { cachedCrop } from "../../../../open_farm/icons";
|
||||
import { cachedCrop } from "../../../../open_farm/cached_crop";
|
||||
import { t } from "../../../../i18next_wrapper";
|
||||
|
||||
/** Return a new plant or plantTemplate object. */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { round, transformXY } from "../../util";
|
||||
import { cachedCrop } from "../../../../open_farm/icons";
|
||||
import { cachedCrop } from "../../../../open_farm/cached_crop";
|
||||
import { MapTransformProps, TaggedPlant } from "../../interfaces";
|
||||
import { SpreadOverlapHelper } from "./spread_overlap_helper";
|
||||
import { BotPosition } from "../../../../devices/interfaces";
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||
import { SpreadOverlapHelperProps } from "../../interfaces";
|
||||
import { round, transformXY } from "../../util";
|
||||
import { BotPosition } from "../../../../devices/interfaces";
|
||||
import { cachedCrop } from "../../../../open_farm/icons";
|
||||
import { cachedCrop } from "../../../../open_farm/cached_crop";
|
||||
import { isUndefined } from "lodash";
|
||||
|
||||
enum OverlapColor {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
jest.mock("react-redux", () => ({
|
||||
connect: jest.fn()
|
||||
}));
|
||||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
let mockPath = "";
|
||||
jest.mock("../../../history", () => ({
|
||||
|
@ -15,6 +13,7 @@ import { history } from "../../../history";
|
|||
import {
|
||||
fakeCropLiveSearchResult
|
||||
} from "../../../__test_support__/fake_crop_search_result";
|
||||
import { svgToUrl } from "../../../open_farm/icons";
|
||||
|
||||
describe("<AddPlant />", () => {
|
||||
const fakeProps = (): AddPlantProps => {
|
||||
|
@ -33,7 +32,7 @@ describe("<AddPlant />", () => {
|
|||
expect(wrapper.text()).toContain("Mint");
|
||||
expect(wrapper.text()).toContain("Done");
|
||||
expect(wrapper.find("img").props().src)
|
||||
.toEqual("data:image/svg+xml;utf8,fake_mint_svg");
|
||||
.toEqual(svgToUrl("fake_mint_svg"));
|
||||
});
|
||||
|
||||
it("goes back", () => {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
jest.mock("react-redux", () => ({
|
||||
connect: jest.fn()
|
||||
}));
|
||||
jest.mock("react-redux", () => ({ connect: jest.fn() }));
|
||||
|
||||
let mockPath = "";
|
||||
jest.mock("../../../history", () => ({
|
||||
|
@ -27,6 +25,7 @@ import {
|
|||
fakeCropLiveSearchResult
|
||||
} from "../../../__test_support__/fake_crop_search_result";
|
||||
import { unselectPlant } from "../../actions";
|
||||
import { svgToUrl } from "../../../open_farm/icons";
|
||||
|
||||
describe("<CropInfo />", () => {
|
||||
const fakeProps = (): CropInfoProps => {
|
||||
|
@ -51,7 +50,7 @@ describe("<CropInfo />", () => {
|
|||
expect(wrapper.text()).toContain("Drag and drop into map");
|
||||
expect(wrapper.text()).toContain("Row Spacing1000mm");
|
||||
expect(wrapper.find("img").last().props().src)
|
||||
.toEqual("data:image/svg+xml;utf8,fake_mint_svg");
|
||||
.toEqual(svgToUrl("fake_mint_svg"));
|
||||
});
|
||||
|
||||
it("navigates to /add", () => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
jest.mock("../../../open_farm/icons", () => ({
|
||||
jest.mock("../../../open_farm/cached_crop", () => ({
|
||||
cachedCrop: jest.fn(() => Promise.resolve({ svg_icon: "icon" })),
|
||||
svgToUrl: jest.fn(x => x),
|
||||
}));
|
||||
|
||||
jest.mock("../../../history", () => ({ push: jest.fn() }));
|
||||
|
@ -15,6 +14,7 @@ import {
|
|||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { Actions } from "../../../constants";
|
||||
import { push } from "../../../history";
|
||||
import { svgToUrl } from "../../../open_farm/icons";
|
||||
|
||||
describe("<PlantInventoryItem />", () => {
|
||||
const fakeProps = (): PlantInventoryItemProps => {
|
||||
|
@ -93,6 +93,6 @@ describe("<PlantInventoryItem />", () => {
|
|||
shallow<PlantInventoryItem>(<PlantInventoryItem {...fakeProps()} />);
|
||||
const img = new Image;
|
||||
await wrapper.find("img").simulate("load", { currentTarget: img });
|
||||
expect(wrapper.state().icon).toEqual("icon");
|
||||
expect(wrapper.state().icon).toEqual(svgToUrl("icon"));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import * as React from "react";
|
||||
|
||||
import moment from "moment";
|
||||
import { DEFAULT_ICON, cachedCrop, svgToUrl } from "../../open_farm/icons";
|
||||
import { DEFAULT_ICON, svgToUrl } from "../../open_farm/icons";
|
||||
import { push } from "../../history";
|
||||
import { Actions } from "../../constants";
|
||||
import { TaggedPlant } from "../map/interfaces";
|
||||
import { get } from "lodash";
|
||||
import { unpackUUID } from "../../util";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { cachedCrop } from "../../open_farm/cached_crop";
|
||||
|
||||
type IMGEvent = React.SyntheticEvent<HTMLImageElement>;
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
jest.mock("axios", () => ({
|
||||
get: () => Promise.resolve({
|
||||
data: {
|
||||
id: 0,
|
||||
data: {
|
||||
attributes: {
|
||||
svg_icon: "<svg>Wow</svg>",
|
||||
slug: "lettuce"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
jest.unmock("../cached_crop");
|
||||
import { cachedCrop } from "../cached_crop";
|
||||
|
||||
describe("cachedIcon()", () => {
|
||||
it("does an HTTP request if the icon can't be found locally", (done) => {
|
||||
cachedCrop("lettuce")
|
||||
.then((item) => {
|
||||
expect(item.svg_icon).toContain("<svg>Wow</svg>");
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
expect(error).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
import { OpenFarmAPI, svgToUrl } from "../icons";
|
||||
|
||||
describe("OpenFarmAPI", () => {
|
||||
it("has a base URL", () => {
|
||||
expect(OpenFarmAPI.OFBaseURL).toContain("openfarm.cc");
|
||||
});
|
||||
});
|
||||
|
||||
describe("svgToUrl()", () => {
|
||||
it("returns svg url", () => {
|
||||
expect(svgToUrl("icon")).toEqual("data:image/svg+xml;utf8,icon");
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
|
||||
jest.mock("axios", function () {
|
||||
return {
|
||||
get: function () {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
id: 0,
|
||||
data: {
|
||||
attributes: {
|
||||
svg_icon: "<svg>Wow</svg>",
|
||||
slug: "lettuce"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
import { cachedCrop, OpenFarmAPI } from "../icons";
|
||||
describe("cachedIcon()", () => {
|
||||
it("does an HTTP request if the icon can't be found locally", (done) => {
|
||||
cachedCrop("lettuce")
|
||||
.then(function (item) {
|
||||
expect(item.svg_icon).toContain("<svg>Wow</svg>");
|
||||
done();
|
||||
})
|
||||
.catch((error) => {
|
||||
expect(error).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("has a base URL", () => {
|
||||
expect(OpenFarmAPI.OFBaseURL).toContain("openfarm.cc");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
import axios, { AxiosResponse } from "axios";
|
||||
import { Dictionary } from "farmbot";
|
||||
import { isObject } from "lodash";
|
||||
import { OFCropAttrs, OFCropResponse, OpenFarmAPI } from "./icons";
|
||||
|
||||
type OFIcon = Readonly<OFCropAttrs>;
|
||||
const STORAGE_KEY = "openfarm_icons_with_spread";
|
||||
|
||||
function initLocalStorage() {
|
||||
localStorage.setItem(STORAGE_KEY, "{}");
|
||||
return {};
|
||||
}
|
||||
|
||||
type IconDictionary = Dictionary<OFIcon | undefined>;
|
||||
|
||||
function getAllIconsFromCache(): IconDictionary {
|
||||
try {
|
||||
const dictionary = JSON.parse(localStorage.getItem(STORAGE_KEY) || "");
|
||||
return isObject(dictionary) ? dictionary as IconDictionary : initLocalStorage();
|
||||
} catch (error) {
|
||||
return initLocalStorage();
|
||||
}
|
||||
}
|
||||
|
||||
function localStorageIconFetch(slug: string): Promise<OFIcon> | undefined {
|
||||
const icon = getAllIconsFromCache()[slug];
|
||||
return icon ? Promise.resolve(icon) : undefined;
|
||||
}
|
||||
|
||||
function localStorageIconSet(icon: OFIcon): void {
|
||||
const dictionary = getAllIconsFromCache();
|
||||
dictionary[icon.slug] = icon;
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(dictionary));
|
||||
}
|
||||
|
||||
/** PROBLEM: HTTP requests get fired too fast. If you have 10 garlic plants,
|
||||
* and the garlic icon is not cached locally, and you try to render 10 garlic
|
||||
* icons in the first 100ms, and HTTP requests take more than 100ms, you will
|
||||
* end up performing 10 HTTP requests at application start time. Not very
|
||||
* efficient */
|
||||
const promiseCache: Dictionary<Promise<Readonly<OFCropAttrs>>> = {};
|
||||
|
||||
function HTTPIconFetch(slug: string) {
|
||||
const url = OpenFarmAPI.OFBaseURL + slug;
|
||||
if (promiseCache[url]) {
|
||||
return promiseCache[url];
|
||||
} else {
|
||||
promiseCache[url] = axios
|
||||
.get<OFCropResponse>(url)
|
||||
.then(cacheTheIcon(slug), cacheTheIcon(slug));
|
||||
return promiseCache[url];
|
||||
}
|
||||
}
|
||||
|
||||
/** PROBLEM: You have 100 lettuce plants. You don't want to download an SVG icon
|
||||
* 100 times.
|
||||
* SOLUTION: Cache stuff. */
|
||||
export function cachedCrop(slug: string): Promise<OFIcon> {
|
||||
return localStorageIconFetch(slug) || HTTPIconFetch(slug);
|
||||
}
|
||||
|
||||
const cacheTheIcon = (slug: string) =>
|
||||
(resp: AxiosResponse<OFCropResponse>): OFIcon => {
|
||||
if (resp
|
||||
&& resp.data
|
||||
&& resp.data.data
|
||||
&& resp.data.data.attributes) {
|
||||
const icon = {
|
||||
slug: resp.data.data.attributes.slug,
|
||||
spread: resp.data.data.attributes.spread,
|
||||
svg_icon: resp.data.data.attributes.svg_icon
|
||||
};
|
||||
localStorageIconSet(icon);
|
||||
return icon;
|
||||
} else {
|
||||
return { slug, spread: undefined, svg_icon: undefined };
|
||||
}
|
||||
};
|
|
@ -1,7 +1,3 @@
|
|||
import axios, { AxiosResponse } from "axios";
|
||||
import { Dictionary } from "farmbot";
|
||||
import { isObject } from "lodash";
|
||||
|
||||
const BASE = "https://openfarm.cc/api/v1/crops/";
|
||||
export const DATA_URI = "data:image/svg+xml;utf8,";
|
||||
export const DEFAULT_ICON = "/app-resources/img/generic-plant.svg";
|
||||
|
@ -28,80 +24,6 @@ export namespace OpenFarmAPI {
|
|||
export let OFBaseURL = BASE;
|
||||
}
|
||||
|
||||
type OFIcon = Readonly<OFCropAttrs>;
|
||||
const STORAGE_KEY = "openfarm_icons_with_spread";
|
||||
|
||||
function initLocalStorage() {
|
||||
localStorage.setItem(STORAGE_KEY, "{}");
|
||||
return {};
|
||||
}
|
||||
|
||||
type IconDictionary = Dictionary<OFIcon | undefined>;
|
||||
|
||||
function getAllIconsFromCache(): IconDictionary {
|
||||
try {
|
||||
const dictionary = JSON.parse(localStorage.getItem(STORAGE_KEY) || "");
|
||||
return isObject(dictionary) ? dictionary as IconDictionary : initLocalStorage();
|
||||
} catch (error) {
|
||||
return initLocalStorage();
|
||||
}
|
||||
}
|
||||
|
||||
function localStorageIconFetch(slug: string): Promise<OFIcon> | undefined {
|
||||
const icon = getAllIconsFromCache()[slug];
|
||||
return icon ? Promise.resolve(icon) : undefined;
|
||||
}
|
||||
|
||||
function localStorageIconSet(icon: OFIcon): void {
|
||||
const dictionary = getAllIconsFromCache();
|
||||
dictionary[icon.slug] = icon;
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(dictionary));
|
||||
}
|
||||
|
||||
/** PROBLEM: HTTP requests get fired too fast. If you have 10 garlic plants,
|
||||
* and the garlic icon is not cached locally, and you try to render 10 garlic
|
||||
* icons in the first 100ms, and HTTP requests take more than 100ms, you will
|
||||
* end up performing 10 HTTP requests at application start time. Not very
|
||||
* efficient */
|
||||
const promiseCache: Dictionary<Promise<Readonly<OFCropAttrs>>> = {};
|
||||
|
||||
function HTTPIconFetch(slug: string) {
|
||||
const url = BASE + slug;
|
||||
if (promiseCache[url]) {
|
||||
return promiseCache[url];
|
||||
} else {
|
||||
promiseCache[url] = axios
|
||||
.get<OFCropResponse>(url)
|
||||
.then(cacheTheIcon(slug), cacheTheIcon(slug));
|
||||
return promiseCache[url];
|
||||
}
|
||||
}
|
||||
|
||||
/** PROBLEM: You have 100 lettuce plants. You don't want to download an SVG icon
|
||||
* 100 times.
|
||||
* SOLUTION: Cache stuff. */
|
||||
export function cachedCrop(slug: string): Promise<OFIcon> {
|
||||
return localStorageIconFetch(slug) || HTTPIconFetch(slug);
|
||||
}
|
||||
|
||||
const cacheTheIcon = (slug: string) =>
|
||||
(resp: AxiosResponse<OFCropResponse>): OFIcon => {
|
||||
if (resp
|
||||
&& resp.data
|
||||
&& resp.data.data
|
||||
&& resp.data.data.attributes) {
|
||||
const icon = {
|
||||
slug: resp.data.data.attributes.slug,
|
||||
spread: resp.data.data.attributes.spread,
|
||||
svg_icon: resp.data.data.attributes.svg_icon
|
||||
};
|
||||
localStorageIconSet(icon);
|
||||
return icon;
|
||||
} else {
|
||||
return { slug, spread: undefined, svg_icon: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
export function svgToUrl(xml: string | undefined): string {
|
||||
return xml ?
|
||||
(DATA_URI + encodeURIComponent(xml)) : DEFAULT_ICON;
|
||||
|
|
Loading…
Reference in New Issue