folders ui tests
parent
8d5218f67c
commit
9c3340be56
|
@ -2,15 +2,12 @@ import {
|
|||
TaggedResource,
|
||||
SpecialStatus,
|
||||
ResourceName,
|
||||
TaggedSequence
|
||||
TaggedSequence,
|
||||
} from "farmbot";
|
||||
import {
|
||||
isTaggedResource,
|
||||
} from "../resources/tagged_resources";
|
||||
import {
|
||||
GetState,
|
||||
ReduxAction
|
||||
} from "../redux/interfaces";
|
||||
import { GetState, ReduxAction } from "../redux/interfaces";
|
||||
import { API } from "./index";
|
||||
import axios from "axios";
|
||||
import {
|
||||
|
@ -18,7 +15,7 @@ import {
|
|||
destroyOK,
|
||||
destroyNO,
|
||||
GeneralizedError,
|
||||
saveOK
|
||||
saveOK,
|
||||
} from "../resources/actions";
|
||||
import { UnsafeError } from "../interfaces";
|
||||
import { defensiveClone, unpackUUID } from "../util";
|
||||
|
@ -284,7 +281,7 @@ export function urlFor(tag: ResourceName) {
|
|||
User: API.current.usersPath,
|
||||
WebAppConfig: API.current.webAppConfigPath,
|
||||
WebcamFeed: API.current.webcamFeedPath,
|
||||
Folder: API.current.foldersPath
|
||||
Folder: API.current.foldersPath,
|
||||
};
|
||||
const url = OPTIONS[tag];
|
||||
if (url) {
|
||||
|
|
|
@ -15,9 +15,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.float-right { float: right; }
|
||||
.float-left { float: left; }
|
||||
|
||||
.hardware-widget {
|
||||
.bp3-popover-wrapper {
|
||||
float: right;
|
||||
|
|
|
@ -230,14 +230,16 @@
|
|||
}
|
||||
|
||||
.folders-panel {
|
||||
height: calc(100vh - 15rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-left: -30px;
|
||||
margin-right: -20px;
|
||||
@media screen and (max-width: 767px) {
|
||||
margin-left: -15px;
|
||||
}
|
||||
.non-empty-state {
|
||||
height: calc(100vh - 15rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.panel-top {
|
||||
margin-left: 1rem !important;
|
||||
button {
|
||||
|
@ -282,8 +284,8 @@
|
|||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
transition: height 0.5s ease-out,
|
||||
padding-top 0.5s ease-out,
|
||||
padding-bottom 0.5s ease-out;
|
||||
padding-top 0.5s ease-out,
|
||||
padding-bottom 0.5s ease-out;
|
||||
transition-delay: 0.4s;
|
||||
color: $gray;
|
||||
font-weight: bold;
|
||||
|
@ -292,8 +294,8 @@
|
|||
cursor: pointer;
|
||||
&.visible {
|
||||
transition: height 0.3s ease-in,
|
||||
padding-top 0.3s ease-in,
|
||||
padding-bottom 0.3s ease-in;
|
||||
padding-top 0.3s ease-in,
|
||||
padding-bottom 0.3s ease-in;
|
||||
transition-delay: 0.2s;
|
||||
height: 3rem;
|
||||
padding-top: 0.5rem;
|
||||
|
@ -322,8 +324,8 @@
|
|||
border-left: 4px solid $dark_gray;
|
||||
}
|
||||
.fa-chevron-down, .fa-chevron-right {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 3rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
@ -391,8 +393,8 @@
|
|||
}
|
||||
}
|
||||
.input {
|
||||
width: 90%;
|
||||
margin: 0.3rem;
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -426,8 +428,8 @@
|
|||
}
|
||||
padding-left: 3rem;
|
||||
.saucer, .icon-saucer {
|
||||
top: 0.55rem;
|
||||
position: relative;
|
||||
top: 0.55rem;
|
||||
margin: auto;
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Everything } from "../interfaces";
|
|||
import { ReduxAction } from "../redux/interfaces";
|
||||
import * as React from "react";
|
||||
import { Actions } from "../constants";
|
||||
import { UUID } from "../resources/interfaces";
|
||||
export const STEP_DATATRANSFER_IDENTIFER = "farmbot/sequence-step";
|
||||
|
||||
/** SIDE EFFECT-Y!! Stores a step into store.draggable.dataTransfer and
|
||||
|
@ -12,7 +13,8 @@ export const STEP_DATATRANSFER_IDENTIFER = "farmbot/sequence-step";
|
|||
export function stepPut(value: Step,
|
||||
ev: React.DragEvent<HTMLElement>,
|
||||
intent: DataXferIntent,
|
||||
draggerId: number):
|
||||
draggerId: number,
|
||||
resourceUuid?: UUID):
|
||||
ReduxAction<DataXferBase> {
|
||||
const uuid = id();
|
||||
ev.dataTransfer.setData(STEP_DATATRANSFER_IDENTIFER, uuid);
|
||||
|
@ -22,7 +24,8 @@ export function stepPut(value: Step,
|
|||
intent,
|
||||
uuid,
|
||||
value,
|
||||
draggerId
|
||||
draggerId,
|
||||
resourceUuid,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { SequenceBodyItem as Step } from "farmbot";
|
||||
import { UUID } from "../resources/interfaces";
|
||||
|
||||
/** An entry in the data transfer table. Used to transfer data from a "draggable"
|
||||
* to a "dropable". For type safety, this is a "tagged union". See Typescript
|
||||
|
@ -17,6 +18,8 @@ export interface DataXferBase {
|
|||
/** "why" the drag/drop event took place (tagged union- See Typescript
|
||||
* documentation for more information). */
|
||||
intent: DataXferIntent;
|
||||
/** Optional resource UUID. */
|
||||
resourceUuid?: UUID;
|
||||
}
|
||||
|
||||
/** Data transfer payload used when moving a *new* step into an existing step */
|
||||
|
@ -51,4 +54,5 @@ export interface StepDraggerProps {
|
|||
intent: DataXferIntent;
|
||||
children?: React.ReactNode;
|
||||
draggerId: number;
|
||||
resourceUuid?: UUID;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from "react";
|
|||
import { stepPut } from "./actions";
|
||||
import { SequenceBodyItem as Step } from "farmbot";
|
||||
import { DataXferIntent, StepDraggerProps } from "./interfaces";
|
||||
import { UUID } from "../resources/interfaces";
|
||||
|
||||
/** Magic number to indicate that the draggerId was not provided or can't be
|
||||
* known. */
|
||||
|
@ -21,22 +22,21 @@ export const NULL_DRAGGER_ID = 0xCAFEF00D;
|
|||
export const stepDragEventHandler = (dispatch: Function,
|
||||
step: Step,
|
||||
intent: DataXferIntent,
|
||||
draggerId: number) => {
|
||||
draggerId: number,
|
||||
resourceUuid?: UUID) => {
|
||||
return (ev: React.DragEvent<HTMLElement>) => {
|
||||
dispatch(stepPut(step, ev, intent, draggerId));
|
||||
dispatch(stepPut(step, ev, intent, draggerId, resourceUuid));
|
||||
};
|
||||
};
|
||||
|
||||
export function StepDragger({ dispatch,
|
||||
step,
|
||||
children,
|
||||
intent,
|
||||
draggerId }: StepDraggerProps) {
|
||||
export function StepDragger(props: StepDraggerProps) {
|
||||
const { dispatch, step, children, intent, draggerId, resourceUuid } = props;
|
||||
return <div className="step-dragger"
|
||||
onDragStart={stepDragEventHandler(dispatch,
|
||||
step,
|
||||
intent,
|
||||
draggerId)}>
|
||||
draggerId,
|
||||
resourceUuid)}>
|
||||
{children}
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
const mockStepGetResult = {
|
||||
value: { kind: "execute", args: { sequence_id: 1 } },
|
||||
resourceUuid: "",
|
||||
};
|
||||
jest.mock("../../draggable/actions", () => ({
|
||||
stepGet: jest.fn(() => () => mockStepGetResult),
|
||||
}));
|
||||
|
||||
import { FolderNode } from "../constants";
|
||||
import { ingest } from "../data_transfer";
|
||||
import {
|
||||
|
@ -11,7 +19,9 @@ import {
|
|||
toggleFolderOpenState,
|
||||
toggleFolderEditState,
|
||||
toggleAll,
|
||||
moveSequence
|
||||
moveSequence,
|
||||
dropSequence,
|
||||
sequenceEditMaybeSave,
|
||||
} from "../actions";
|
||||
import { sample } from "lodash";
|
||||
import { cloneAndClimb, climb } from "../climb";
|
||||
|
@ -24,6 +34,8 @@ 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.
|
||||
```
|
||||
|
@ -76,7 +88,7 @@ const mockState: DeepPartial<Everything> =
|
|||
jest.mock("../../redux/store", () => {
|
||||
return {
|
||||
store: {
|
||||
dispatch: jest.fn(),
|
||||
dispatch: jest.fn(x => typeof x === "function" && x()),
|
||||
getState: jest.fn(() => mockState)
|
||||
}
|
||||
};
|
||||
|
@ -146,10 +158,6 @@ export const TEST_GRAPH = ingest({
|
|||
}
|
||||
});
|
||||
|
||||
describe("deletion of folders", () => {
|
||||
test.todo("can't delete populated folders");
|
||||
});
|
||||
|
||||
describe("expand/collapse all", () => {
|
||||
const halfOpen = cloneAndClimb(TEST_GRAPH, (node) => {
|
||||
node.open = !sample([true, false]);
|
||||
|
@ -209,6 +217,16 @@ describe("createFolder", () => {
|
|||
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", () => {
|
||||
|
@ -259,6 +277,24 @@ describe("toggleAll", () => {
|
|||
});
|
||||
});
|
||||
|
||||
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";
|
||||
|
@ -276,3 +312,30 @@ describe("moveSequence", () => {
|
|||
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 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,73 @@
|
|||
jest.mock("../actions", () => ({
|
||||
updateSearchTerm: jest.fn(),
|
||||
toggleAll: jest.fn(),
|
||||
moveSequence: jest.fn(),
|
||||
dropSequence: jest.fn(() => jest.fn()),
|
||||
sequenceEditMaybeSave: jest.fn(),
|
||||
deleteFolder: jest.fn(),
|
||||
toggleFolderEditState: jest.fn(),
|
||||
createFolder: jest.fn(),
|
||||
addNewSequenceToFolder: jest.fn(),
|
||||
setFolderName: jest.fn(),
|
||||
toggleFolderOpenState: jest.fn(),
|
||||
setFolderColor: jest.fn(),
|
||||
}));
|
||||
|
||||
let mockPath = "";
|
||||
jest.mock("../../history", () => ({
|
||||
history: { getCurrentLocation: () => ({ pathname: mockPath }) }
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { Folders } from "../component";
|
||||
import { FolderProps } from "../constants";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import {
|
||||
Folders, FolderPanelTop, SequenceDropArea, FolderNameEditor,
|
||||
FolderButtonCluster, FolderListItem, FolderNameInput,
|
||||
} from "../component";
|
||||
import {
|
||||
FolderProps, FolderPanelTopProps, SequenceDropAreaProps, FolderNodeProps,
|
||||
FolderNodeInitial, FolderButtonClusterProps, FolderItemProps,
|
||||
FolderNameInputProps,
|
||||
FolderNodeMedial,
|
||||
FolderNodeTerminal,
|
||||
} from "../constants";
|
||||
import {
|
||||
updateSearchTerm, toggleAll, moveSequence, dropSequence,
|
||||
sequenceEditMaybeSave,
|
||||
deleteFolder,
|
||||
toggleFolderEditState,
|
||||
createFolder,
|
||||
addNewSequenceToFolder,
|
||||
setFolderName,
|
||||
toggleFolderOpenState,
|
||||
setFolderColor,
|
||||
} from "../actions";
|
||||
import { fakeSequence } from "../../__test_support__/fake_state/resources";
|
||||
import { SpecialStatus, Color } from "farmbot";
|
||||
|
||||
const fakeRootFolder = (): FolderNodeInitial => ({
|
||||
kind: "initial",
|
||||
children: [],
|
||||
id: 1,
|
||||
name: "my folder",
|
||||
content: [],
|
||||
color: "gray",
|
||||
open: true,
|
||||
editing: false,
|
||||
});
|
||||
|
||||
const fakeFolderNode = (): FolderNodeMedial => {
|
||||
const folder = fakeRootFolder() as unknown as FolderNodeMedial;
|
||||
folder.kind = "medial";
|
||||
return folder;
|
||||
};
|
||||
|
||||
const fakeTerminalFolder = (): FolderNodeTerminal => {
|
||||
const folder = fakeRootFolder() as unknown as FolderNodeTerminal;
|
||||
folder.children = undefined;
|
||||
folder.kind = "terminal";
|
||||
return folder;
|
||||
};
|
||||
|
||||
describe("<Folders />", () => {
|
||||
const fakeProps = (): FolderProps => ({
|
||||
|
@ -21,4 +87,460 @@ describe("<Folders />", () => {
|
|||
const wrapper = mount<Folders>(<Folders {...p} />);
|
||||
expect(wrapper.text()).toContain("No Sequences.");
|
||||
});
|
||||
|
||||
it("renders sequences outside of folders", () => {
|
||||
const p = fakeProps();
|
||||
p.rootFolder.folders[0] = fakeRootFolder();
|
||||
const sequence = fakeSequence();
|
||||
p.sequences = { [sequence.uuid]: sequence };
|
||||
sequence.body.name = "my sequence";
|
||||
p.rootFolder.noFolder = [sequence.uuid];
|
||||
const wrapper = mount<Folders>(<Folders {...p} />);
|
||||
expect(wrapper.text()).toContain("my sequence");
|
||||
});
|
||||
|
||||
it("renders empty folder", () => {
|
||||
const p = fakeProps();
|
||||
p.rootFolder.folders[0] = fakeRootFolder();
|
||||
const wrapper = mount<Folders>(<Folders {...p} />);
|
||||
expect(wrapper.text()).toContain("my folder");
|
||||
});
|
||||
|
||||
it("renders sequences in folder", () => {
|
||||
const p = fakeProps();
|
||||
const sequence = fakeSequence();
|
||||
sequence.body.name = "my sequence";
|
||||
p.sequences = { [sequence.uuid]: sequence };
|
||||
const folder = fakeRootFolder();
|
||||
folder.content = [sequence.uuid];
|
||||
p.rootFolder.folders[0] = folder;
|
||||
const wrapper = mount<Folders>(<Folders {...p} />);
|
||||
expect(wrapper.text()).toContain("my sequence");
|
||||
});
|
||||
|
||||
it("renders folders in folder", () => {
|
||||
const p = fakeProps();
|
||||
const folder = fakeRootFolder();
|
||||
const childFolder = fakeFolderNode();
|
||||
childFolder.name = "deeper folder";
|
||||
folder.children = [childFolder];
|
||||
p.rootFolder.folders[0] = folder;
|
||||
const wrapper = mount<Folders>(<Folders {...p} />);
|
||||
expect(wrapper.text()).toContain("deeper folder");
|
||||
});
|
||||
|
||||
it("renders terminal folder", () => {
|
||||
const p = fakeProps();
|
||||
const folder = fakeRootFolder();
|
||||
folder.name = "folder";
|
||||
const childFolder = fakeFolderNode();
|
||||
childFolder.name = "deeper folder";
|
||||
const terminalFolder = fakeTerminalFolder();
|
||||
terminalFolder.name = "deepest folder";
|
||||
childFolder.children = [terminalFolder];
|
||||
folder.children = [childFolder];
|
||||
p.rootFolder.folders[0] = folder;
|
||||
const wrapper = mount<Folders>(<Folders {...p} />);
|
||||
["folder", "deeper folder", "deepest folder"].map(string =>
|
||||
expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
||||
it("toggles all folders", () => {
|
||||
const wrapper = mount<Folders>(<Folders {...fakeProps()} />);
|
||||
expect(wrapper.state().toggleDirection).toEqual(false);
|
||||
wrapper.instance().toggleAll();
|
||||
expect(toggleAll).toHaveBeenCalledWith(false);
|
||||
expect(wrapper.state().toggleDirection).toEqual(true);
|
||||
});
|
||||
|
||||
it("starts sequence move", () => {
|
||||
const wrapper = mount<Folders>(<Folders {...fakeProps()} />);
|
||||
expect(wrapper.state().movedSequenceUuid).toEqual(undefined);
|
||||
wrapper.instance().startSequenceMove("fakeUuid");
|
||||
expect(wrapper.state().movedSequenceUuid).toEqual("fakeUuid");
|
||||
expect(wrapper.state().stashedUuid).toEqual(undefined);
|
||||
});
|
||||
|
||||
const toggleMoveTest = (p: {
|
||||
prev: string | undefined,
|
||||
current: string | undefined,
|
||||
arg: string | undefined,
|
||||
new: string | undefined
|
||||
}) => {
|
||||
const wrapper = mount<Folders>(<Folders {...fakeProps()} />);
|
||||
wrapper.setState({ movedSequenceUuid: p.current, stashedUuid: p.prev });
|
||||
wrapper.instance().toggleSequenceMove(p.arg);
|
||||
expect(wrapper.state().movedSequenceUuid).toEqual(p.new);
|
||||
};
|
||||
|
||||
it("toggle sequence move: on", () => {
|
||||
toggleMoveTest({
|
||||
prev: undefined, current: undefined, arg: "fakeUuid", new: "fakeUuid"
|
||||
});
|
||||
toggleMoveTest({
|
||||
prev: undefined, current: "oldFakeUuid", arg: "fakeUuid", new: "fakeUuid"
|
||||
});
|
||||
});
|
||||
|
||||
it("toggle sequence move: off", () => {
|
||||
toggleMoveTest({
|
||||
prev: undefined, current: undefined, arg: undefined, new: undefined
|
||||
});
|
||||
toggleMoveTest({
|
||||
prev: "fakeUuid", current: "fakeUuid", arg: undefined, new: undefined
|
||||
});
|
||||
toggleMoveTest({
|
||||
prev: "fakeUuid", current: undefined, arg: "fakeUuid", new: undefined
|
||||
});
|
||||
toggleMoveTest({
|
||||
prev: "fakeUuid", current: "fakeUuid", arg: "fakeUuid", new: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it("ends sequence move", () => {
|
||||
const wrapper = mount<Folders>(<Folders {...fakeProps()} />);
|
||||
wrapper.setState({ movedSequenceUuid: "fakeUuid" });
|
||||
wrapper.instance().endSequenceMove(1);
|
||||
expect(moveSequence).toHaveBeenCalledWith("fakeUuid", 1);
|
||||
expect(wrapper.state().movedSequenceUuid).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("ends sequence move: undefined", () => {
|
||||
const wrapper = mount<Folders>(<Folders {...fakeProps()} />);
|
||||
wrapper.setState({ movedSequenceUuid: undefined });
|
||||
wrapper.instance().endSequenceMove(1);
|
||||
expect(moveSequence).toHaveBeenCalledWith("", 1);
|
||||
expect(wrapper.state().movedSequenceUuid).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<FolderListItem />", () => {
|
||||
const fakeProps = (): FolderItemProps => ({
|
||||
startSequenceMove: jest.fn(),
|
||||
toggleSequenceMove: jest.fn(),
|
||||
sequence: fakeSequence(),
|
||||
movedSequenceUuid: undefined,
|
||||
dispatch: jest.fn(),
|
||||
variableData: undefined,
|
||||
inUse: false,
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const p = fakeProps();
|
||||
p.sequence.body.name = "my sequence";
|
||||
const wrapper = mount(<FolderListItem {...p} />);
|
||||
expect(wrapper.text()).toContain("my sequence");
|
||||
expect(wrapper.find("li").hasClass("move-source")).toBeFalsy();
|
||||
expect(wrapper.find("li").hasClass("active")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("renders: move in progress", () => {
|
||||
const p = fakeProps();
|
||||
p.movedSequenceUuid = p.sequence.uuid;
|
||||
const wrapper = mount(<FolderListItem {...p} />);
|
||||
expect(wrapper.find("li").hasClass("move-source")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders: active", () => {
|
||||
const p = fakeProps();
|
||||
p.sequence.body.name = "sequence";
|
||||
mockPath = "/app/sequences/sequence";
|
||||
const wrapper = mount(<FolderListItem {...p} />);
|
||||
expect(wrapper.find("li").hasClass("active")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders: unsaved", () => {
|
||||
const p = fakeProps();
|
||||
p.sequence.body.name = "my sequence";
|
||||
p.sequence.specialStatus = SpecialStatus.DIRTY;
|
||||
const wrapper = mount(<FolderListItem {...p} />);
|
||||
expect(wrapper.text()).toContain("my sequence*");
|
||||
});
|
||||
|
||||
it("renders: in use", () => {
|
||||
const p = fakeProps();
|
||||
p.inUse = true;
|
||||
const wrapper = mount(<FolderListItem {...p} />);
|
||||
expect(wrapper.find(".in-use").length).toEqual(1);
|
||||
});
|
||||
|
||||
it("changes color", () => {
|
||||
const p = fakeProps();
|
||||
p.sequence.body.id = undefined;
|
||||
p.sequence.body.name = "";
|
||||
p.sequence.body.color = "" as Color;
|
||||
const wrapper = shallow(<FolderListItem {...p} />);
|
||||
wrapper.find("ColorPicker").simulate("change", "green");
|
||||
expect(sequenceEditMaybeSave).toHaveBeenCalledWith(p.sequence, {
|
||||
color: "green"
|
||||
});
|
||||
});
|
||||
|
||||
it("starts sequence move", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<FolderListItem {...p} />);
|
||||
wrapper.find(".fa-bars").simulate("mouseDown");
|
||||
expect(p.startSequenceMove).toHaveBeenCalledWith(p.sequence.uuid);
|
||||
});
|
||||
|
||||
it("toggles sequence move", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<FolderListItem {...p} />);
|
||||
wrapper.find(".fa-bars").simulate("mouseUp");
|
||||
expect(p.toggleSequenceMove).toHaveBeenCalledWith(p.sequence.uuid);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<FolderButtonCluster />", () => {
|
||||
const fakeProps = (): FolderButtonClusterProps => ({
|
||||
node: fakeRootFolder(),
|
||||
close: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<FolderButtonCluster {...fakeProps()} />);
|
||||
expect(wrapper.find("button").length).toEqual(4);
|
||||
});
|
||||
|
||||
it("deletes folder", () => {
|
||||
const p = fakeProps();
|
||||
p.node.id = 1;
|
||||
const wrapper = mount(<FolderButtonCluster {...p} />);
|
||||
wrapper.find("button").at(0).simulate("click");
|
||||
expect(deleteFolder).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("edits folder", () => {
|
||||
const p = fakeProps();
|
||||
p.node.id = 1;
|
||||
const wrapper = mount(<FolderButtonCluster {...p} />);
|
||||
wrapper.find("button").at(1).simulate("click");
|
||||
expect(p.close).toHaveBeenCalled();
|
||||
expect(toggleFolderEditState).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("creates new folder", () => {
|
||||
const p = fakeProps();
|
||||
p.node.id = 1;
|
||||
const wrapper = mount(<FolderButtonCluster {...p} />);
|
||||
wrapper.find("button").at(2).simulate("click");
|
||||
expect(p.close).toHaveBeenCalled();
|
||||
expect(createFolder).toHaveBeenCalledWith({ parent_id: p.node.id });
|
||||
});
|
||||
|
||||
it("creates new sequence", () => {
|
||||
const p = fakeProps();
|
||||
p.node.id = 1;
|
||||
const wrapper = mount(<FolderButtonCluster {...p} />);
|
||||
wrapper.find("button").at(3).simulate("click");
|
||||
expect(p.close).toHaveBeenCalled();
|
||||
expect(addNewSequenceToFolder).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<FolderNameInput />", () => {
|
||||
const fakeProps = (): FolderNameInputProps => ({
|
||||
node: fakeFolderNode(),
|
||||
});
|
||||
|
||||
it("edits folder name", () => {
|
||||
const p = fakeProps();
|
||||
p.node.editing = true;
|
||||
const wrapper = shallow(<FolderNameInput {...p} />);
|
||||
wrapper.find("BlurableInput").simulate("commit", {
|
||||
currentTarget: { value: "new name" }
|
||||
});
|
||||
expect(setFolderName).toHaveBeenCalledWith(p.node.id, "new name");
|
||||
});
|
||||
|
||||
it("closes folder name input", () => {
|
||||
const p = fakeProps();
|
||||
p.node.editing = true;
|
||||
const wrapper = shallow(<FolderNameInput {...p} />);
|
||||
wrapper.find("button").simulate("click");
|
||||
expect(toggleFolderEditState).toHaveBeenCalledWith(p.node.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<FolderNameEditor />", () => {
|
||||
const fakeProps = (): FolderNodeProps => ({
|
||||
node: fakeRootFolder(),
|
||||
sequences: {},
|
||||
movedSequenceUuid: undefined,
|
||||
startSequenceMove: jest.fn(),
|
||||
toggleSequenceMove: jest.fn(),
|
||||
onMoveEnd: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
resourceUsage: {},
|
||||
sequenceMetas: {},
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<FolderNameEditor {...p} />);
|
||||
expect(wrapper.text()).toContain("my folder");
|
||||
expect(wrapper.find(".fa-ellipsis-v").hasClass("open")).toBeFalsy();
|
||||
expect(wrapper.find(".fa-chevron-down").length).toEqual(1);
|
||||
expect(wrapper.find(".fa-chevron-right").length).toEqual(0);
|
||||
expect(wrapper.find(".folder-name-input").length).toEqual(0);
|
||||
});
|
||||
|
||||
it("renders: settings open", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<FolderNameEditor>(<FolderNameEditor {...p} />);
|
||||
wrapper.setState({ settingsOpen: true });
|
||||
expect(wrapper.find(".fa-ellipsis-v").hasClass("open")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders: folder closed", () => {
|
||||
const p = fakeProps();
|
||||
p.node.open = false;
|
||||
const wrapper = mount(<FolderNameEditor {...p} />);
|
||||
expect(wrapper.find(".fa-chevron-down").length).toEqual(0);
|
||||
expect(wrapper.find(".fa-chevron-right").length).toEqual(1);
|
||||
});
|
||||
|
||||
it("renders: editing", () => {
|
||||
const p = fakeProps();
|
||||
p.node.editing = true;
|
||||
const wrapper = mount(<FolderNameEditor {...p} />);
|
||||
expect(wrapper.find(".folder-name-input").length).toEqual(1);
|
||||
});
|
||||
|
||||
it("closes folder", () => {
|
||||
const p = fakeProps();
|
||||
p.node.open = true;
|
||||
const wrapper = mount(<FolderNameEditor {...p} />);
|
||||
wrapper.find("i").first().simulate("click");
|
||||
expect(toggleFolderOpenState).toHaveBeenCalledWith(p.node.id);
|
||||
});
|
||||
|
||||
it("changes folder color", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<FolderNameEditor {...p} />);
|
||||
wrapper.find("ColorPicker").simulate("change", "green");
|
||||
expect(setFolderColor).toHaveBeenCalledWith(p.node.id, "green");
|
||||
});
|
||||
|
||||
it("opens settings menu", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow<FolderNameEditor>(<FolderNameEditor {...p} />);
|
||||
expect(wrapper.state().settingsOpen).toBeFalsy();
|
||||
wrapper.find("i").last().simulate("click");
|
||||
expect(wrapper.state().settingsOpen).toBeTruthy();
|
||||
});
|
||||
|
||||
it("closes settings menu", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount<FolderNameEditor>(<FolderNameEditor {...p} />);
|
||||
wrapper.setState({ settingsOpen: true });
|
||||
wrapper.instance().close();
|
||||
expect(wrapper.state().settingsOpen).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("<SequenceDropArea />", () => {
|
||||
const fakeProps = (): SequenceDropAreaProps => ({
|
||||
dropAreaVisible: true,
|
||||
onMoveEnd: jest.fn(),
|
||||
toggleSequenceMove: jest.fn(),
|
||||
folderId: 1,
|
||||
folderName: "my folder",
|
||||
});
|
||||
|
||||
it("shows drop area", () => {
|
||||
const p = fakeProps();
|
||||
p.dropAreaVisible = true;
|
||||
const wrapper = mount(<SequenceDropArea {...p} />);
|
||||
expect(wrapper.find(".folder-drop-area").hasClass("visible")).toBeTruthy();
|
||||
expect(wrapper.text().toLowerCase()).toContain("move into my folder");
|
||||
});
|
||||
|
||||
it("hides drop area", () => {
|
||||
const p = fakeProps();
|
||||
p.dropAreaVisible = false;
|
||||
const wrapper = mount(<SequenceDropArea {...p} />);
|
||||
expect(wrapper.find(".folder-drop-area").hasClass("visible")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("has 'remove from folders' text", () => {
|
||||
const p = fakeProps();
|
||||
p.dropAreaVisible = true;
|
||||
p.folderId = 0;
|
||||
const wrapper = mount(<SequenceDropArea {...p} />);
|
||||
expect(wrapper.find(".folder-drop-area").hasClass("visible")).toBeTruthy();
|
||||
expect(wrapper.text()).not.toContain("my folder");
|
||||
expect(wrapper.text().toLowerCase()).toContain("move out of folders");
|
||||
});
|
||||
|
||||
it("handles click", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<SequenceDropArea {...p} />);
|
||||
wrapper.find(".folder-drop-area").simulate("click");
|
||||
expect(p.onMoveEnd).toHaveBeenCalledWith(p.folderId);
|
||||
});
|
||||
|
||||
it("handles drop", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow<SequenceDropArea>(<SequenceDropArea {...p} />);
|
||||
wrapper.setState({ hovered: true });
|
||||
expect(wrapper.find(".folder-drop-area").hasClass("hovered")).toBeTruthy();
|
||||
wrapper.find(".folder-drop-area").simulate("drop");
|
||||
expect(wrapper.state().hovered).toBeFalsy();
|
||||
expect(dropSequence).toHaveBeenCalledWith(p.folderId);
|
||||
expect(p.toggleSequenceMove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles drag over", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<SequenceDropArea {...p} />);
|
||||
const e = { preventDefault: jest.fn() };
|
||||
wrapper.find(".folder-drop-area").simulate("dragOver", e);
|
||||
expect(e.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles drag enter", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow<SequenceDropArea>(<SequenceDropArea {...p} />);
|
||||
wrapper.find(".folder-drop-area").simulate("dragEnter");
|
||||
expect(wrapper.state().hovered).toBeTruthy();
|
||||
});
|
||||
|
||||
it("handles drag leave", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow<SequenceDropArea>(<SequenceDropArea {...p} />);
|
||||
wrapper.find(".folder-drop-area").simulate("dragLeave");
|
||||
expect(wrapper.state().hovered).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("<FolderPanelTop />", () => {
|
||||
const fakeProps = (): FolderPanelTopProps => ({
|
||||
searchTerm: "",
|
||||
toggleDirection: true,
|
||||
toggleAll: jest.fn(),
|
||||
});
|
||||
|
||||
it("changes search term", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<FolderPanelTop {...p} />);
|
||||
wrapper.find("input").simulate("change", {
|
||||
currentTarget: { value: "new" }
|
||||
});
|
||||
expect(updateSearchTerm).toHaveBeenCalledWith("new");
|
||||
});
|
||||
|
||||
it("creates new folder", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<FolderPanelTop {...p} />);
|
||||
wrapper.find("button").at(1).simulate("click");
|
||||
expect(createFolder).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it("creates new sequence", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<FolderPanelTop {...p} />);
|
||||
wrapper.find("button").at(2).simulate("click");
|
||||
expect(addNewSequenceToFolder).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -118,7 +118,8 @@ export const dropSequence = (folder_id: number) =>
|
|||
const dataXferObj = dispatch(stepGet(key));
|
||||
const { sequence_id } = dataXferObj.value.args;
|
||||
const ri = store.getState().resources.index;
|
||||
const seqUuid = ri.byKindAndId[joinKindAndId("Sequence", sequence_id)];
|
||||
const seqUuid = dataXferObj.resourceUuid ||
|
||||
ri.byKindAndId[joinKindAndId("Sequence", sequence_id)];
|
||||
const sequence = maybeGetSequence(ri, seqUuid);
|
||||
if (sequence) { sequenceEditMaybeSave(sequence, { folder_id }); }
|
||||
};
|
||||
|
|
|
@ -62,7 +62,8 @@ export const FolderListItem = (props: FolderItemProps) => {
|
|||
body: variableList(props.variableData)
|
||||
}}
|
||||
intent="step_splice"
|
||||
draggerId={NULL_DRAGGER_ID}>
|
||||
draggerId={NULL_DRAGGER_ID}
|
||||
resourceUuid={sequence.uuid}>
|
||||
<li className={`sequence-list-item ${active} ${moveSource}`}
|
||||
draggable={true}>
|
||||
<ColorPicker
|
||||
|
@ -91,7 +92,7 @@ const ToggleFolderBtn = (props: ToggleFolderBtnProps) => {
|
|||
const AddFolderBtn = ({ folder, close }: AddFolderBtn) => {
|
||||
return <button
|
||||
className="fb-button green"
|
||||
onClick={() => { close?.(); createFolder(folder || {}); }}>
|
||||
onClick={() => { close?.(); createFolder(folder); }}>
|
||||
<div className="fa-stack fa-2x" title={"Create Subfolder"}>
|
||||
<i className="fa fa-folder fa-stack-2x" />
|
||||
<i className="fa fa-plus fa-stack-1x" />
|
||||
|
@ -129,7 +130,7 @@ export const FolderButtonCluster =
|
|||
</div>;
|
||||
};
|
||||
|
||||
const FolderNameInput = ({ node }: FolderNameInputProps) =>
|
||||
export const FolderNameInput = ({ node }: FolderNameInputProps) =>
|
||||
<div className="folder-name-input">
|
||||
<BlurableInput value={node.name} onCommit={e =>
|
||||
setFolderName(node.id, e.currentTarget.value)} />
|
||||
|
@ -143,6 +144,7 @@ const FolderNameInput = ({ node }: FolderNameInputProps) =>
|
|||
export class FolderNameEditor
|
||||
extends React.Component<FolderNodeProps, FolderNodeState> {
|
||||
state: FolderNodeState = { settingsOpen: false };
|
||||
close = () => this.setState({ settingsOpen: false });
|
||||
render() {
|
||||
const { node } = this.props;
|
||||
const settingsOpenClass = this.state.settingsOpen ? "open" : "";
|
||||
|
@ -164,8 +166,7 @@ export class FolderNameEditor
|
|||
<i className={`fa fa-ellipsis-v ${settingsOpenClass}`}
|
||||
onClick={() =>
|
||||
this.setState({ settingsOpen: !this.state.settingsOpen })} />
|
||||
<FolderButtonCluster {...this.props}
|
||||
close={() => this.setState({ settingsOpen: false })} />
|
||||
<FolderButtonCluster {...this.props} close={this.close} />
|
||||
</Popover>
|
||||
</div>;
|
||||
}
|
||||
|
@ -322,9 +323,7 @@ export const FolderPanelTop = (props: FolderPanelTopProps) =>
|
|||
<i className="fa fa-search" />
|
||||
<input
|
||||
value={props.searchTerm || ""}
|
||||
onChange={({ currentTarget }) => {
|
||||
updateSearchTerm(currentTarget.value);
|
||||
}}
|
||||
onChange={e => updateSearchTerm(e.currentTarget.value)}
|
||||
type="text"
|
||||
placeholder={t("Search sequences")} />
|
||||
</div>
|
||||
|
|
|
@ -94,7 +94,8 @@ export interface FolderNodeProps {
|
|||
sequenceMetas: Record<UUID, VariableNameSet | undefined>;
|
||||
}
|
||||
|
||||
export interface FolderButtonClusterProps extends FolderNodeProps {
|
||||
export interface FolderButtonClusterProps {
|
||||
node: FolderUnion;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import { fakeResource } from "../../__test_support__/fake_resource";
|
|||
import { resourceReducer } from "../reducer";
|
||||
import { findByUuid } from "../reducer_support";
|
||||
import { EditResourceParams } from "../../api/interfaces";
|
||||
import { fakeFolder } from "../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("resource reducer", () => {
|
||||
it("marks resources as DIRTY when reducing OVERWRITE_RESOURCE", () => {
|
||||
|
@ -114,6 +115,17 @@ describe("resource reducer", () => {
|
|||
.concat(["Image", "SensorReading"])
|
||||
.map((kind: ResourceName) => testResourceDestroy(kind));
|
||||
});
|
||||
|
||||
it("toggles folder open state", () => {
|
||||
const folder = fakeFolder();
|
||||
folder.body.id = 1;
|
||||
const startingState = buildResourceIndex([folder]);
|
||||
delete startingState.index.sequenceFolders.localMetaAttributes[1].open;
|
||||
const action = { type: Actions.FOLDER_TOGGLE, payload: { id: 1 } };
|
||||
const newState = resourceReducer(startingState, action);
|
||||
expect(newState.index.sequenceFolders.localMetaAttributes[1].open)
|
||||
.toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findByUuid", () => {
|
||||
|
|
|
@ -17,7 +17,6 @@ import { resourceReducer } from "../reducer";
|
|||
import { emptyState } from "../reducer";
|
||||
import { resourceReady, newTaggedResource } from "../../sync/actions";
|
||||
import { chain } from "lodash";
|
||||
// import { Actions } from "../../constants";
|
||||
|
||||
const TOOL_ID = 99;
|
||||
const SLOT_ID = 100;
|
||||
|
@ -156,6 +155,7 @@ describe("getSequenceByUUID()", () => {
|
|||
expect(console.warn).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserAccountSettings", () => {
|
||||
it("throws exceptions when user is not loaded", () => {
|
||||
const boom = () => Selector
|
||||
|
@ -164,6 +164,7 @@ describe("getUserAccountSettings", () => {
|
|||
.toThrow("PROBLEM: Tried to fetch user before it was available.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("maybeGetSequence", () => {
|
||||
it("returns undefined", () => {
|
||||
const i = buildResourceIndex([]);
|
||||
|
@ -236,6 +237,13 @@ describe("findRegimenById()", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("findFolderById()", () => {
|
||||
it("throws error", () => {
|
||||
const find = () => Selector.findFolderById(fakeIndex, 0);
|
||||
expect(find).toThrow("Bad folder id: 0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("maybeFindPlantById()", () => {
|
||||
it("not found", () => {
|
||||
const result = Selector.maybeFindPlantById(fakeIndex, 0);
|
||||
|
|
|
@ -1,68 +1,44 @@
|
|||
import * as React from "react";
|
||||
import { AllSteps } from "../all_steps";
|
||||
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
|
||||
import { AllSteps, AllStepsProps } from "../all_steps";
|
||||
import { shallow } from "enzyme";
|
||||
import { TaggedSequence, SpecialStatus } from "farmbot";
|
||||
import { TileMoveRelative } from "../step_tiles/tile_move_relative";
|
||||
import { TileReadPin } from "../step_tiles/tile_read_pin";
|
||||
import { TileWritePin } from "../step_tiles/tile_write_pin";
|
||||
import { sanitizeNodes } from "../locals_list/sanitize_nodes";
|
||||
import { fakeSequence } from "../../__test_support__/fake_state/resources";
|
||||
import { fakeResourceIndex } from "../locals_list/test_helpers";
|
||||
import { maybeTagStep } from "../../resources/sequence_tagging";
|
||||
import { DropArea } from "../../draggable/drop_area";
|
||||
|
||||
describe("<AllSteps/>", () => {
|
||||
const TEST_CASE: TaggedSequence = {
|
||||
"kind": "Sequence",
|
||||
"specialStatus": SpecialStatus.SAVED,
|
||||
"body": sanitizeNodes({
|
||||
"id": 8,
|
||||
"name": "Goto 0, 0, 0",
|
||||
"folder_id": undefined,
|
||||
"color": "gray",
|
||||
"body": [
|
||||
{
|
||||
"kind": "move_relative",
|
||||
"args": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"speed": 100
|
||||
},
|
||||
},
|
||||
{
|
||||
"kind": "read_pin",
|
||||
"args": {
|
||||
"pin_number": 0,
|
||||
"pin_mode": 0,
|
||||
"label": "---"
|
||||
},
|
||||
},
|
||||
{
|
||||
"kind": "write_pin",
|
||||
"args": {
|
||||
"pin_number": 0,
|
||||
"pin_value": 0,
|
||||
"pin_mode": 0
|
||||
},
|
||||
}
|
||||
],
|
||||
"args": {
|
||||
"locals": { kind: "scope_declaration", args: {} },
|
||||
"version": 4,
|
||||
},
|
||||
"kind": "sequence"
|
||||
}).thisSequence,
|
||||
"uuid": "Sequence.8.52"
|
||||
};
|
||||
const fakeProps = (): AllStepsProps => ({
|
||||
sequence: fakeSequence(),
|
||||
onDrop: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
resources: fakeResourceIndex(),
|
||||
confirmStepDeletion: true,
|
||||
});
|
||||
|
||||
it("uses index as a key", () => {
|
||||
const el = shallow(<AllSteps
|
||||
sequence={TEST_CASE}
|
||||
onDrop={() => { }}
|
||||
dispatch={jest.fn()}
|
||||
resources={buildResourceIndex([]).index}
|
||||
confirmStepDeletion={false} />);
|
||||
[TileMoveRelative, TileReadPin, TileWritePin]
|
||||
.map(q => {
|
||||
expect(el.find(q).length).toEqual(1);
|
||||
});
|
||||
it("renders empty sequence", () => {
|
||||
const wrapper = shallow(<AllSteps {...fakeProps()} />);
|
||||
expect(wrapper.html()).toEqual("<div class=\"all-steps\"></div>");
|
||||
});
|
||||
|
||||
it("renders steps", () => {
|
||||
const p = fakeProps();
|
||||
p.sequence.body.body = [
|
||||
{ kind: "move_relative", args: { x: 0, y: 0, z: 0, speed: 100 } },
|
||||
{ kind: "read_pin", args: { pin_number: 0, pin_mode: 0, label: "---" } },
|
||||
{ kind: "write_pin", args: { pin_number: 0, pin_value: 0, pin_mode: 0 } }
|
||||
];
|
||||
p.sequence.body.body.map(step => maybeTagStep(step));
|
||||
const wrapper = shallow(<AllSteps {...p} />);
|
||||
["TileMoveRelative", "TileReadPin", "TileWritePin"]
|
||||
.map(element => expect(wrapper.find(element).length).toEqual(1));
|
||||
});
|
||||
|
||||
it("calls onDrop", () => {
|
||||
const p = fakeProps();
|
||||
p.sequence.body.body = [{ kind: "wait", args: { milliseconds: 0 } }];
|
||||
p.sequence.body.body.map(step => maybeTagStep(step));
|
||||
const wrapper = shallow(<AllSteps {...p} />);
|
||||
wrapper.find<DropArea>(DropArea).props().callback?.("fake key");
|
||||
expect(p.onDrop).toHaveBeenCalledWith(0, "fake key");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,7 +44,6 @@ describe("<Sequences/>", () => {
|
|||
|
||||
it("renders", () => {
|
||||
const wrapper = shallow(<Sequences {...fakeProps()} />);
|
||||
debugger;
|
||||
expect(wrapper.html()).toContain("Sequences");
|
||||
expect(wrapper.html()).toContain("Edit Sequence");
|
||||
expect(wrapper.html()).toContain(ToolTips.SEQUENCE_EDITOR);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { HardwareFlags, FarmwareInfo } from "./interfaces";
|
|||
import { ShouldDisplay } from "../devices/interfaces";
|
||||
import { AddCommandButton } from "./sequence_editor_middle_active";
|
||||
|
||||
interface AllStepsProps {
|
||||
export interface AllStepsProps {
|
||||
sequence: TaggedSequence;
|
||||
onDrop(index: number, key: string): void;
|
||||
dispatch: Function;
|
||||
|
@ -25,9 +25,7 @@ interface AllStepsProps {
|
|||
|
||||
export class AllSteps extends React.Component<AllStepsProps, {}> {
|
||||
render() {
|
||||
const {
|
||||
sequence, onDrop, dispatch, hardwareFlags, farmwareInfo, shouldDisplay
|
||||
} = this.props;
|
||||
const { sequence, dispatch } = this.props;
|
||||
const items = (sequence.body.body || [])
|
||||
.map((currentStep: SequenceBodyItem, index) => {
|
||||
/** HACK: React's diff algorithm (probably?) can't keep track of steps
|
||||
|
@ -39,22 +37,22 @@ export class AllSteps extends React.Component<AllStepsProps, {}> {
|
|||
return <div className="sequence-steps"
|
||||
key={readThatCommentAbove}>
|
||||
<AddCommandButton dispatch={dispatch} index={index} />
|
||||
<DropArea callback={(key) => onDrop(index, key)} />
|
||||
<DropArea callback={key => this.props.onDrop(index, key)} />
|
||||
<StepDragger
|
||||
dispatch={dispatch}
|
||||
step={currentStep}
|
||||
intent="step_move"
|
||||
draggerId={index}>
|
||||
<div>
|
||||
<div className="sequence-step">
|
||||
{renderCeleryNode({
|
||||
currentStep,
|
||||
index,
|
||||
dispatch,
|
||||
currentSequence: sequence,
|
||||
resources: this.props.resources,
|
||||
hardwareFlags,
|
||||
farmwareInfo,
|
||||
shouldDisplay,
|
||||
hardwareFlags: this.props.hardwareFlags,
|
||||
farmwareInfo: this.props.farmwareInfo,
|
||||
shouldDisplay: this.props.shouldDisplay,
|
||||
confirmStepDeletion: this.props.confirmStepDeletion,
|
||||
showPins: this.props.showPins,
|
||||
expandStepOptions: this.props.expandStepOptions,
|
||||
|
@ -64,6 +62,6 @@ export class AllSteps extends React.Component<AllStepsProps, {}> {
|
|||
</div>;
|
||||
});
|
||||
|
||||
return <div> {items} </div>;
|
||||
return <div className="all-steps">{items}</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,12 @@ import * as React from "react";
|
|||
import { connect } from "react-redux";
|
||||
import { StepButtonCluster } from "./step_button_cluster";
|
||||
import { SequenceEditorMiddle } from "./sequence_editor_middle";
|
||||
import { Page, Row, LeftPanel } from "../ui";
|
||||
import { Page, Row, LeftPanel, CenterPanel, RightPanel } from "../ui";
|
||||
import { Props } from "./interfaces";
|
||||
import { mapStateToProps } from "./state_to_props";
|
||||
import { ToolTips } from "../constants";
|
||||
import { isTaggedSequence } from "../resources/tagged_resources";
|
||||
import { setActiveSequenceByName } from "./set_active_sequence_by_name";
|
||||
import { CenterPanel, RightPanel } from "../ui";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { unselectSequence, closeCommandMenu } from "./actions";
|
||||
import { isNumber } from "lodash";
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import {
|
||||
ColorPicker,
|
||||
ColorPickerProps,
|
||||
ColorPickerCluster,
|
||||
ColorPickerClusterProps,
|
||||
} from "../color_picker";
|
||||
|
||||
describe("<ColorPicker />", () => {
|
||||
const fakeProps = (): ColorPickerProps => ({
|
||||
current: "green",
|
||||
onChange: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders saucers", () => {
|
||||
const wrapper = mount(<ColorPicker {...fakeProps()} />);
|
||||
expect(wrapper.find(".saucer").length).toEqual(1);
|
||||
expect(wrapper.find(".green").length).toEqual(1);
|
||||
});
|
||||
|
||||
it("renders icon saucers", () => {
|
||||
const p = fakeProps();
|
||||
p.saucerIcon = "fa-check";
|
||||
const wrapper = mount(<ColorPicker {...p} />);
|
||||
expect(wrapper.find(".icon-saucer").length).toEqual(1);
|
||||
expect(wrapper.find(".green").length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<ColorPickerCluster />", () => {
|
||||
const fakeProps = (): ColorPickerClusterProps => ({
|
||||
current: "green",
|
||||
onChange: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders saucers", () => {
|
||||
const wrapper = mount(<ColorPickerCluster {...fakeProps()} />);
|
||||
expect(wrapper.find(".saucer").length).toEqual(8);
|
||||
});
|
||||
|
||||
it("renders icon saucers", () => {
|
||||
const p = fakeProps();
|
||||
p.saucerIcon = "fa-check";
|
||||
const wrapper = mount(<ColorPickerCluster {...p} />);
|
||||
expect(wrapper.find("i").length).toEqual(8);
|
||||
});
|
||||
|
||||
it("changes color", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<ColorPickerCluster {...p} />);
|
||||
wrapper.find("div").at(1).simulate("click");
|
||||
expect(p.onChange).toHaveBeenCalledWith("blue");
|
||||
});
|
||||
});
|
|
@ -4,14 +4,14 @@ import { Saucer } from "../ui/index";
|
|||
import { ResourceColor } from "../interfaces";
|
||||
import { colors } from "../util";
|
||||
|
||||
interface PickerProps {
|
||||
export interface ColorPickerProps {
|
||||
position?: Position;
|
||||
current: ResourceColor;
|
||||
onChange?: (color: ResourceColor) => void;
|
||||
onChange: (color: ResourceColor) => void;
|
||||
saucerIcon?: string;
|
||||
}
|
||||
|
||||
interface ColorPickerClusterProps {
|
||||
export interface ColorPickerClusterProps {
|
||||
onChange: (color: ResourceColor) => void;
|
||||
current: ResourceColor;
|
||||
saucerIcon?: string;
|
||||
|
@ -42,10 +42,9 @@ export const ColorPickerCluster = (props: ColorPickerClusterProps) => {
|
|||
})}
|
||||
</div>;
|
||||
};
|
||||
export class ColorPicker extends React.Component<PickerProps, {}> {
|
||||
export class ColorPicker extends React.Component<ColorPickerProps, {}> {
|
||||
|
||||
public render() {
|
||||
const cb = this.props.onChange || function () { };
|
||||
return <Popover className="color-picker"
|
||||
position={this.props.position || Position.BOTTOM}
|
||||
popoverClassName="colorpicker-menu gray">
|
||||
|
@ -54,7 +53,7 @@ export class ColorPicker extends React.Component<PickerProps, {}> {
|
|||
this.props.current}`} />
|
||||
: <Saucer color={this.props.current} />}
|
||||
<ColorPickerCluster
|
||||
onChange={cb}
|
||||
onChange={this.props.onChange}
|
||||
current={this.props.current}
|
||||
saucerIcon={this.props.saucerIcon} />
|
||||
</Popover>;
|
||||
|
|
Loading…
Reference in New Issue