Farmbot-Web-App/webpack/devices/__tests__/actions_test.ts

505 lines
16 KiB
TypeScript

const mockDevice = {
checkUpdates: jest.fn(() => { return Promise.resolve(); }),
powerOff: jest.fn(() => { return Promise.resolve(); }),
resetOS: jest.fn(),
reboot: jest.fn(() => { return Promise.resolve(); }),
checkArduinoUpdates: jest.fn(() => { return Promise.resolve(); }),
emergencyLock: jest.fn(() => { return Promise.resolve(); }),
emergencyUnlock: jest.fn(() => { return Promise.resolve(); }),
execSequence: jest.fn(() => { return Promise.resolve(); }),
resetMCU: jest.fn(() => { return Promise.resolve(); }),
updateMcu: jest.fn(() => { return Promise.resolve(); }),
togglePin: jest.fn(() => { return Promise.resolve(); }),
home: jest.fn(() => { return Promise.resolve(); }),
sync: jest.fn(() => { return Promise.resolve(); }),
readStatus: jest.fn(() => Promise.resolve()),
updateConfig: jest.fn(() => Promise.resolve()),
dumpInfo: jest.fn(() => Promise.resolve()),
};
jest.mock("../../device", () => ({
getDevice: () => {
return mockDevice;
}
}));
jest.mock("farmbot-toastr", () => ({
success: jest.fn(),
info: jest.fn(),
error: jest.fn(),
warning: jest.fn(),
}));
let mockGetRelease: Promise<{}> = Promise.resolve({});
jest.mock("axios", () => ({
default: {
get: jest.fn(() => { return mockGetRelease; })
}
}));
import * as actions from "../actions";
import {
fakeSequence, fakeFbosConfig, fakeFirmwareConfig
} from "../../__test_support__/fake_state/resources";
import { fakeState } from "../../__test_support__/fake_state";
import { changeStepSize, resetNetwork, resetConnectionInfo } from "../actions";
import { Actions } from "../../constants";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
import { API } from "../../api/index";
import axios from "axios";
import { SpecialStatus } from "../../resources/tagged_resources";
import { McuParamName } from "farmbot";
import { bot } from "../../__test_support__/fake_state/bot";
import { success, error, warning, info } from "farmbot-toastr";
describe("checkControllerUpdates()", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("calls checkUpdates", async () => {
await actions.checkControllerUpdates();
expect(mockDevice.checkUpdates).toHaveBeenCalled();
expect(success).toHaveBeenCalled();
});
});
describe("powerOff()", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("calls powerOff", async () => {
await actions.powerOff();
expect(mockDevice.powerOff).toHaveBeenCalled();
expect(success).toHaveBeenCalled();
});
});
describe("factoryReset()", () => {
beforeEach(function () {
jest.clearAllMocks();
});
it("doesn't call factoryReset", async () => {
window.confirm = () => false;
await actions.factoryReset();
expect(mockDevice.resetOS).not.toHaveBeenCalled();
});
it("calls factoryReset", async () => {
window.confirm = () => true;
await actions.factoryReset();
expect(mockDevice.resetOS).toHaveBeenCalled();
});
});
describe("reboot()", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("calls reboot", async () => {
await actions.reboot();
expect(mockDevice.reboot).toHaveBeenCalled();
expect(success).toHaveBeenCalled();
});
});
describe("emergencyLock() / emergencyUnlock", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("calls emergencyLock", () => {
actions.emergencyLock();
expect(mockDevice.emergencyLock).toHaveBeenCalled();
});
it("calls emergencyUnlock", () => {
window.confirm = () => true;
actions.emergencyUnlock();
expect(mockDevice.emergencyUnlock).toHaveBeenCalled();
});
});
describe("sync()", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("doesn't call sync: disconnected", () => {
const getState = () => fakeState();
actions.sync()(jest.fn(), getState);
expect(mockDevice.sync).not.toHaveBeenCalled();
const expectedMessage = ["FarmBot is not connected.", "Disconnected", "red"];
expect(info).toBeCalledWith(...expectedMessage);
});
});
describe("execSequence()", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("calls execSequence", async () => {
const s = fakeSequence().body;
await actions.execSequence(s);
expect(mockDevice.execSequence).toHaveBeenCalledWith(s.id);
expect(success).toHaveBeenCalled();
});
it("implodes when executing unsaved sequences", () => {
const ok = fakeSequence().body;
ok.id = undefined;
expect(() => actions.execSequence(ok)).toThrow();
expect(mockDevice.execSequence).not.toHaveBeenCalled();
});
});
describe("MCUFactoryReset()", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("doesn't call resetMCU", () => {
window.confirm = () => false;
actions.MCUFactoryReset();
expect(mockDevice.resetMCU).not.toHaveBeenCalled();
});
it("calls resetMCU", () => {
window.confirm = () => true;
actions.MCUFactoryReset();
expect(mockDevice.resetMCU).toHaveBeenCalled();
});
});
describe("requestDiagnostic", () => {
it("requests that FBOS build a diagnostic report", () => {
actions.requestDiagnostic();
expect(mockDevice.dumpInfo).toHaveBeenCalled();
});
});
describe("settingToggle()", () => {
beforeEach(function () {
jest.clearAllMocks();
});
it("toggles mcu param via updateMcu", async () => {
bot.hardware.mcu_params.param_mov_nr_retry = 0;
const sourceSetting = (x: McuParamName) =>
({ value: bot.hardware.mcu_params[x], consistent: true });
const state = fakeState();
const fakeConfig = fakeFirmwareConfig();
fakeConfig.body.api_migrated = false;
state.resources = buildResourceIndex([fakeConfig]);
await actions.settingToggle(
"param_mov_nr_retry", sourceSetting)(jest.fn(), () => state);
expect(mockDevice.updateMcu)
.toHaveBeenCalledWith({ param_mov_nr_retry: 1 });
});
it("toggles mcu param via FirmwareConfig", async () => {
bot.hardware.mcu_params.param_mov_nr_retry = 1;
const sourceSetting = (x: McuParamName) =>
({ value: bot.hardware.mcu_params[x], consistent: true });
const state = fakeState();
const fakeConfig = fakeFirmwareConfig();
fakeConfig.body.api_migrated = true;
state.resources = buildResourceIndex([fakeConfig]);
const dispatch = jest.fn();
await actions.settingToggle(
"param_mov_nr_retry", sourceSetting)(dispatch, () => state);
expect(mockDevice.updateMcu).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith({
payload: {
specialStatus: SpecialStatus.DIRTY,
update: { param_mov_nr_retry: 0 },
uuid: expect.stringContaining("FirmwareConfig")
},
type: Actions.EDIT_RESOURCE
});
});
});
describe("updateMCU()", () => {
beforeEach(function () {
jest.clearAllMocks();
});
it("updates mcu param via updateMcu", async () => {
bot.hardware.mcu_params.param_mov_nr_retry = 0;
const state = fakeState();
const fakeConfig = fakeFirmwareConfig();
fakeConfig.body.api_migrated = false;
state.resources = buildResourceIndex([fakeConfig]);
await actions.updateMCU(
"param_mov_nr_retry", "1")(jest.fn(), () => state);
expect(mockDevice.updateMcu)
.toHaveBeenCalledWith({ param_mov_nr_retry: "1" });
});
it("updates mcu param via FirmwareConfig", async () => {
bot.hardware.mcu_params.param_mov_nr_retry = 1;
const state = fakeState();
const fakeConfig = fakeFirmwareConfig();
fakeConfig.body.api_migrated = true;
state.resources = buildResourceIndex([fakeConfig]);
const dispatch = jest.fn();
await actions.updateMCU(
"param_mov_nr_retry", "0")(dispatch, () => state);
expect(mockDevice.updateMcu).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith({
payload: {
specialStatus: SpecialStatus.DIRTY,
update: { param_mov_nr_retry: "0" },
uuid: expect.stringContaining("FirmwareConfig")
},
type: Actions.EDIT_RESOURCE
});
});
it("prevents update with incompatible value", async () => {
bot.hardware.mcu_params.movement_max_spd_x = 0;
const state = fakeState();
const fakeConfig = fakeFirmwareConfig();
fakeConfig.body.api_migrated = false;
state.resources = buildResourceIndex([fakeConfig]);
await actions.updateMCU(
"movement_min_spd_x", "100")(jest.fn(), () => state);
expect(mockDevice.updateMcu).not.toHaveBeenCalled();
expect(warning).toHaveBeenCalledWith(
"Minimum speed should always be lower than maximum");
});
});
describe("pinToggle()", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("calls togglePin", async () => {
await actions.pinToggle(5);
expect(mockDevice.togglePin).toHaveBeenCalledWith({ pin_number: 5 });
expect(success).not.toHaveBeenCalled();
});
});
describe("homeAll()", function () {
beforeEach(function () {
jest.clearAllMocks();
});
it("calls home", async () => {
await actions.homeAll(100);
expect(mockDevice.home)
.toHaveBeenCalledWith({ axis: "all", speed: 100 });
expect(success).not.toHaveBeenCalled();
});
});
describe("isLog()", function () {
it("knows if it is a log or not", () => {
expect(actions.isLog({})).toBe(false);
expect(actions.isLog({ message: "foo" })).toBe(true);
});
});
describe("toggleControlPanel()", function () {
it("toggles", () => {
const action = actions.toggleControlPanel("homing_and_calibration");
expect(action.payload).toEqual("homing_and_calibration");
});
});
describe("changeStepSize()", () => {
it("returns a redux action", () => {
const payload = 23;
const result = changeStepSize(payload);
expect(result.type).toBe(Actions.CHANGE_STEP_SIZE);
expect(result.payload).toBe(payload);
});
});
describe("resetNetwork()", () => {
it("renders correct info", () => {
const result = resetNetwork();
expect(result.payload).toEqual({});
expect(result.type).toEqual(Actions.RESET_NETWORK);
});
});
describe("resetConnectionInfo()", () => {
it("dispatches the right actions", () => {
const mock1 = jest.fn();
API.setBaseUrl("http://localhost:300");
resetConnectionInfo()(mock1);
expect(mock1).toHaveBeenCalledWith(resetNetwork());
expect(mock1).toHaveBeenCalledTimes(1);
expect(mockDevice.readStatus).toHaveBeenCalled();
});
});
describe("fetchReleases()", () => {
beforeEach(function () {
jest.clearAllMocks();
});
it("fetches latest OS release version", async () => {
mockGetRelease = Promise.resolve({ data: { tag_name: "v1.0.0" } });
const dispatch = jest.fn();
await actions.fetchReleases("url")(dispatch);
expect(axios.get).toHaveBeenCalledWith("url");
expect(error).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith({
payload: { version: "1.0.0", commit: undefined },
type: Actions.FETCH_OS_UPDATE_INFO_OK
});
});
it("fetches latest beta OS release version", async () => {
mockGetRelease = Promise.resolve({
data: { tag_name: "v1.0.0-beta", target_commitish: "commit" }
});
const dispatch = jest.fn();
await actions.fetchReleases("url", { beta: true })(dispatch);
expect(axios.get).toHaveBeenCalledWith("url");
expect(error).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith({
payload: { version: "1.0.0-beta", commit: "commit" },
type: Actions.FETCH_BETA_OS_UPDATE_INFO_OK
});
});
it("fails to fetches latest OS release version", async () => {
mockGetRelease = Promise.reject("error");
const dispatch = jest.fn();
await actions.fetchReleases("url")(dispatch);
await expect(axios.get).toHaveBeenCalledWith("url");
expect(error).toHaveBeenCalledWith(
"Could not download FarmBot OS update information.");
expect(dispatch).toHaveBeenCalledWith({
payload: "error",
type: "FETCH_OS_UPDATE_INFO_ERROR"
});
});
it("fails to fetches latest beta OS release version", async () => {
mockGetRelease = Promise.reject("error");
const dispatch = jest.fn();
await actions.fetchReleases("url", { beta: true })(dispatch);
await expect(axios.get).toHaveBeenCalledWith("url");
expect(error).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith({
payload: "error",
type: "FETCH_BETA_OS_UPDATE_INFO_ERROR"
});
});
});
describe("fetchMinOsFeatureData()", () => {
afterEach(() =>
jest.restoreAllMocks());
it("fetches min OS feature data: empty", async () => {
mockGetRelease = Promise.resolve({ data: {} });
const dispatch = jest.fn();
await actions.fetchMinOsFeatureData("url")(dispatch);
expect(axios.get).toHaveBeenCalledWith("url");
expect(dispatch).toHaveBeenCalledWith({
payload: {},
type: Actions.FETCH_MIN_OS_FEATURE_INFO_OK
});
});
it("fetches min OS feature data", async () => {
mockGetRelease = Promise.resolve({
data: { "a_feature": "1.0.0", "b_feature": "2.0.0" }
});
const dispatch = jest.fn();
await actions.fetchMinOsFeatureData("url")(dispatch);
expect(axios.get).toHaveBeenCalledWith("url");
expect(dispatch).toHaveBeenCalledWith({
payload: { a_feature: "1.0.0", b_feature: "2.0.0" },
type: Actions.FETCH_MIN_OS_FEATURE_INFO_OK
});
});
it("fetches bad min OS feature data: not an object", async () => {
mockGetRelease = Promise.resolve({ data: "bad" });
const dispatch = jest.fn();
const mockConsole = jest.spyOn(console, "log").mockImplementation(() => { });
await actions.fetchMinOsFeatureData("url")(dispatch);
expect(axios.get).toHaveBeenCalledWith("url");
expect(dispatch).not.toHaveBeenCalled();
expect(mockConsole).toHaveBeenCalledWith(
expect.stringContaining("\"bad\""));
});
it("fetches bad min OS feature data", async () => {
mockGetRelease = Promise.resolve({ data: { a: "0", b: 0 } });
const dispatch = jest.fn();
const mockConsole = jest.spyOn(console, "log").mockImplementation(() => { });
await actions.fetchMinOsFeatureData("url")(dispatch);
expect(axios.get).toHaveBeenCalledWith("url");
expect(dispatch).not.toHaveBeenCalled();
expect(mockConsole).toHaveBeenCalledWith(
expect.stringContaining("{\"a\":\"0\",\"b\":0}"));
});
it("fails to fetch min OS feature data", async () => {
mockGetRelease = Promise.reject("error");
const dispatch = jest.fn();
await actions.fetchMinOsFeatureData("url")(dispatch);
await expect(axios.get).toHaveBeenCalledWith("url");
expect(error).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith({
payload: "error",
type: "FETCH_MIN_OS_FEATURE_INFO_ERROR"
});
});
});
describe("updateConfig()", () => {
beforeEach(function () {
jest.clearAllMocks();
});
it("updates config: configUpdate", () => {
const dispatch = jest.fn();
const state = fakeState();
state.resources.index = buildResourceIndex([fakeFbosConfig()]).index;
actions.updateConfig({ auto_sync: true })(dispatch, () => state);
expect(mockDevice.updateConfig).toHaveBeenCalledWith({ auto_sync: true });
expect(dispatch).not.toHaveBeenCalled();
});
it("updates config: FbosConfig", () => {
const dispatch = jest.fn(() => Promise.resolve());
const state = fakeState();
const fakeFBOSConfig = fakeFbosConfig();
fakeFBOSConfig.body.api_migrated = true;
state.resources.index = buildResourceIndex([fakeFBOSConfig]).index;
actions.updateConfig({ auto_sync: true })(dispatch, () => state);
expect(mockDevice.updateConfig).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledWith({
payload: {
specialStatus: SpecialStatus.DIRTY,
update: { auto_sync: true },
uuid: expect.stringContaining("FbosConfig")
},
type: Actions.EDIT_RESOURCE
});
});
});
describe("badVersion()", () => {
it("warns of old FBOS version", () => {
actions.badVersion();
expect(info).toHaveBeenCalledWith(
expect.stringContaining("old version"), "Please Update", "red");
});
});