Farmbot-Web-App/frontend/folders/__tests__/actions_test.ts

342 lines
11 KiB
TypeScript

const mockStepGetResult = {
value: { kind: "execute", args: { sequence_id: 1 } },
resourceUuid: "",
};
jest.mock("../../draggable/actions", () => ({
stepGet: jest.fn(() => () => mockStepGetResult),
}));
import { FolderNode } from "../interfaces";
import { ingest } from "../data_transfer";
import {
collapseAll,
setFolderColor,
setFolderName,
addNewSequenceToFolder,
createFolder,
deleteFolder,
updateSearchTerm,
toggleFolderOpenState,
toggleFolderEditState,
toggleAll,
moveSequence,
dropSequence,
sequenceEditMaybeSave,
} from "../actions";
import { sample } from "lodash";
import { cloneAndClimb, climb } from "../climb";
import { store } from "../../redux/store";
import { DeepPartial } from "redux";
import { Everything } from "../../interfaces";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
import { newTaggedResource } from "../../sync/actions";
import { save, edit, init, initSave, destroy } from "../../api/crud";
import { setActiveSequenceByName } from "../../sequences/set_active_sequence_by_name";
import { push } from "../../history";
import { fakeSequence } from "../../__test_support__/fake_state/resources";
import { stepGet } from "../../draggable/actions";
import { SpecialStatus } from "farmbot";
/** A set of fake Folder resources used exclusively for testing purposes.
```
├─ One
├─ Two
│ └─ Three
├─ Four
│ └─ Five
├─ Six
│ └─ Seven
│ ├─ Eight
│ └─ Nine
├─ Ten
│ ├─ Eleven
│ └─ Twelve
│ └─ Thirteen
└─ Fourteen
├─ Fifteen
└─ Sixteen
├─ Seventeen
└─ Eighteen
``` */
const mockFolders: FolderNode[] = [
{ id: 1, parent_id: undefined, color: "blue", name: "One" },
{ id: 2, parent_id: undefined, color: "blue", name: "Two" },
{ id: 3, parent_id: 2, color: "blue", name: "Three" },
{ id: 4, parent_id: undefined, color: "blue", name: "Four" },
{ id: 5, parent_id: 4, color: "blue", name: "Five" },
{ id: 6, parent_id: undefined, color: "blue", name: "Six" },
{ id: 7, parent_id: 6, color: "blue", name: "Seven" },
{ id: 8, parent_id: 7, color: "blue", name: "Eight" },
{ id: 9, parent_id: 7, color: "blue", name: "Nine" },
{ id: 10, parent_id: undefined, color: "blue", name: "Ten" },
{ id: 11, parent_id: 10, color: "blue", name: "Eleven" },
{ id: 12, parent_id: 10, color: "blue", name: "Twelve" },
{ id: 13, parent_id: 12, color: "blue", name: "Thirteen" },
{ id: 14, parent_id: undefined, color: "blue", name: "Fourteen" },
{ id: 15, parent_id: 14, color: "blue", name: "Fifteen" },
{ id: 16, parent_id: 14, color: "blue", name: "Sixteen" },
{ id: 17, parent_id: 16, color: "blue", name: "Seventeen" },
{ id: 18, parent_id: 16, color: "blue", name: "Eighteen" }
];
const mockSequence = fakeSequence();
const i = buildResourceIndex(newTaggedResource("Folder", mockFolders));
const mockState: DeepPartial<Everything> =
({ resources: buildResourceIndex([mockSequence], i) });
jest.mock("../../redux/store", () => {
return {
store: {
dispatch: jest.fn(x => typeof x === "function" && x()),
getState: jest.fn(() => mockState)
}
};
});
jest.mock("../../api/crud", () => {
return {
destroy: jest.fn(),
edit: jest.fn(),
init: jest.fn(),
initSave: jest.fn(),
save: jest.fn()
};
});
jest.mock("../../sequences/set_active_sequence_by_name", () => {
return { setActiveSequenceByName: jest.fn() };
});
jest.mock("../../history", () => {
return { push: jest.fn() };
});
/**
```
├─ One
├─ Two
│ └─ Three
├─ Four
│ └─ Five
├─ Six
│ └─ Seven
│ ├─ Eight
│ └─ Nine
├─ Ten
│ ├─ Eleven
│ └─ Twelve
│ └─ Thirteen
└─ Fourteen
├─ Fifteen
└─ Sixteen
├─ Seventeen
└─ Eighteen
```
*/
export const TEST_GRAPH = ingest({
folders: mockFolders,
localMetaAttributes: {
[1]: { editing: false, open: true, sequences: ["childOfFolder1"] },
[2]: { editing: false, open: true, sequences: ["childOfFolder2"] },
[3]: { editing: false, open: true, sequences: ["childOfFolder3"] },
[4]: { editing: false, open: true, sequences: ["childOfFolder4"] },
[5]: { editing: false, open: true, sequences: ["childOfFolder5"] },
[6]: { editing: false, open: true, sequences: ["childOfFolder6"] },
[7]: { editing: false, open: true, sequences: ["childOfFolder7"] },
[8]: { editing: false, open: true, sequences: ["childOfFolder8"] },
[9]: { editing: false, open: true, sequences: ["childOfFolder9"] },
[10]: { editing: false, open: true, sequences: ["childOfFolder10"] },
[11]: { editing: false, open: true, sequences: ["childOfFolder11"] },
[12]: { editing: false, open: true, sequences: ["childOfFolder12"] },
[13]: { editing: false, open: true, sequences: ["childOfFolder13"] },
[14]: { editing: false, open: true, sequences: ["childOfFolder14"] },
[15]: { editing: false, open: true, sequences: ["childOfFolder15"] },
[16]: { editing: false, open: true, sequences: ["childOfFolder16"] },
[17]: { editing: false, open: true, sequences: ["childOfFolder17"] },
[18]: { editing: false, open: true, sequences: ["childOfFolder18"] },
}
});
describe("expand/collapse all", () => {
const halfOpen = cloneAndClimb(TEST_GRAPH, (node) => {
node.open = !sample([true, false]);
});
it("collapses all folders", async () => {
const closed = await collapseAll(halfOpen);
climb(closed, (node) => {
expect(node.open).toBe(false);
});
});
});
describe("setFolderColor", () => {
it("updates a folder's color", () => {
setFolderColor(11, "blue");
const uuid = expect.stringContaining("Folder.11.");
const body = expect.objectContaining({ color: "blue" });
const resource = expect.objectContaining({ uuid, body });
expect(store.dispatch).toHaveBeenCalled();
expect(save).toHaveBeenCalledWith(uuid);
expect(edit).toHaveBeenCalledWith(resource, body);
});
});
describe("setFolderName", () => {
it("updates a folder's name", () => {
setFolderName(11, "Harold");
const uuid = expect.stringContaining("Folder.11.");
const body = expect.objectContaining({ name: "Harold" });
const resource = expect.objectContaining({ uuid });
expect(store.dispatch).toHaveBeenCalled();
expect(edit).toHaveBeenCalledWith(resource, body);
expect(save).toHaveBeenCalledWith(uuid);
});
});
describe("addNewSequenceToFolder", () => {
it("Adds a new sequence to a folder", () => {
addNewSequenceToFolder(11);
expect(setActiveSequenceByName).toHaveBeenCalled();
expect(init).toHaveBeenCalledWith("Sequence", expect.objectContaining({
name: "new sequence 1"
}));
expect(push).toHaveBeenCalledWith("/app/sequences/new_sequence_1");
});
});
describe("createFolder", () => {
it("saves a new folder", () => {
createFolder({ name: "test case 1" });
expect(store.dispatch).toHaveReturnedTimes(1);
expect(initSave).toHaveBeenCalledWith("Folder", {
color: "gray",
name: "test case 1",
parent_id: 0
});
});
it("saves a new folder without inputs", () => {
createFolder();
expect(store.dispatch).toHaveReturnedTimes(1);
expect(initSave).toHaveBeenCalledWith("Folder", {
color: "gray",
name: "New Folder",
parent_id: 0
});
});
});
describe("deleteFolder", () => {
it("deletes a folder", () => {
const uuid = expect.stringContaining("Folder.12.");
deleteFolder(12);
expect(store.dispatch).toHaveBeenCalled();
expect(destroy).toHaveBeenCalledWith(uuid);
});
});
describe("updateSearchTerm", () => {
it("updates a search term", () => {
const args =
(payload: string | undefined) => ({ type: "FOLDER_SEARCH", payload });
[undefined, "foo"].map(term => {
updateSearchTerm(term);
expect(store.dispatch).toHaveBeenCalledWith(args(term));
});
});
});
describe("toggleFolderOpenState", () => {
it("dispatches the correct action", () => {
const id = 12;
toggleFolderOpenState(id);
expect(store.dispatch)
.toHaveBeenCalledWith({ type: "FOLDER_TOGGLE", payload: { id } });
});
});
describe("toggleFolderEditState", () => {
it("dispatches the correct action", () => {
const id = 12;
toggleFolderEditState(id);
expect(store.dispatch)
.toHaveBeenCalledWith({ type: "FOLDER_TOGGLE_EDIT", payload: { id } });
});
});
describe("toggleAll", () => {
it("toggles all folders", () => {
[true, false].map(payload => {
toggleAll(payload);
expect(store.dispatch)
.toHaveBeenCalledWith({ type: "FOLDER_TOGGLE_ALL", payload });
});
});
});
describe("sequenceEditMaybeSave()", () => {
it("saves", () => {
const sequence = fakeSequence();
sequence.specialStatus = SpecialStatus.SAVED;
sequenceEditMaybeSave(sequence, {});
expect(edit).toHaveBeenCalled();
expect(save).toHaveBeenCalledWith(sequence.uuid);
});
it("doesn't save", () => {
const sequence = fakeSequence();
sequence.specialStatus = SpecialStatus.DIRTY;
sequenceEditMaybeSave(sequence, {});
expect(edit).toHaveBeenCalled();
expect(save).not.toHaveBeenCalled();
});
});
describe("moveSequence", () => {
it("silently fails when given bad UUIDs", () => {
const uuid = "a.b.c";
moveSequence(uuid, 123);
expect(store.dispatch).not.toHaveBeenCalled();
});
it("moves a sequence", () => {
const uuid = mockSequence.uuid;
moveSequence(uuid, 12);
expect(store.dispatch).toHaveBeenCalled();
const update1 = expect.objectContaining({ uuid });
const update2 = expect.objectContaining({ folder_id: 12 });
expect(edit).toHaveBeenCalledWith(update1, update2);
expect(save).toHaveBeenCalledWith(uuid);
});
});
describe("dropSequence()", () => {
const fakeDragEvent = ({
dataTransfer: { getData: () => "fakeKey" }
} as unknown as React.DragEvent<HTMLElement>);
it("updates folder_id", () => {
dropSequence(1)(fakeDragEvent);
expect(stepGet).toHaveBeenCalledWith("fakeKey");
expect(edit).toHaveBeenCalledWith(mockSequence, { folder_id: 1 });
});
it("handles missing sequence", () => {
mockStepGetResult.value.args.sequence_id = -1;
dropSequence(1)(fakeDragEvent);
expect(stepGet).toHaveBeenCalledWith("fakeKey");
expect(edit).not.toHaveBeenCalled();
});
it("gets sequence by UUID", () => {
mockStepGetResult.value.args.sequence_id = -1;
mockStepGetResult.resourceUuid = mockSequence.uuid;
dropSequence(1)(fakeDragEvent);
expect(stepGet).toHaveBeenCalledWith("fakeKey");
expect(edit).toHaveBeenCalledWith(mockSequence, { folder_id: 1 });
});
});