Adds tests for all components

This commit is contained in:
Patrick McDonagh
2018-04-13 15:52:24 -05:00
parent 568a3eebb3
commit 9f97e05a22
17 changed files with 9580 additions and 283 deletions

View File

@@ -0,0 +1,145 @@
import React from "react";
import { shallow, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
import _ from "lodash";
import { Controls, mapStateToProps } from "../../app/src/components/Controls";
describe("Controls", () => {
const tags = {
cfg_PID_FlowSP: { value: 0 },
cfg_PID_FluidLevelSP: { value: 0 },
cfg_PID_TubingPressureSP: { value: 0 },
cfg_PID_ManualSP: { value: 0 },
Device_Status_INT: { value: 0 },
sts_PID_Control: { value: 5 }
};
it("should render loading if no tags prop", () => {
const wrapper = shallow(<Controls tags={undefined}/>);
expect(wrapper.find(".loading-notags")).toHaveLength(1);
});
it("should render loading if no Device_Status_INT in tags", () => {
const wrapper = shallow(<Controls tags={_.omit(tags, "Device_Status_INT")} />);
expect(wrapper.find(".loading-nostatus")).toHaveLength(1);
});
it("should render controls div", () => {
const wrapper = shallow(<Controls tags={tags} />);
expect(wrapper.find(".controls")).toHaveLength(1);
});
describe("control parameters", () => {
let writeTag;
beforeEach(() => {
writeTag = jest.fn((tag, val) => {
return {tag, val};
});
});
it("should run the writeTag function with cmd_Start and true on writeStart", () => {
tags.Device_Status_INT.value = 4;
const wrapper = shallow(<Controls tags={tags} writeTag={writeTag} />);
wrapper.find(".start-button").simulate("click", null);
expect(writeTag).toHaveBeenCalledWith("cmd_Start", true);
});
it("should run the writeTag function with cmd_Stop and true on writeStop", () => {
tags.Device_Status_INT.value = 0;
const wrapper = shallow(<Controls tags={tags} writeTag={writeTag} />);
wrapper.find(".stop-button").simulate("click", null);
expect(writeTag).toHaveBeenCalledWith("cmd_Stop", true);
});
it("should disable set button on flowrate select button if parameter already selected", () => {
tags.sts_PID_Control.value = 0;
const wrapper = shallow(<Controls tags={tags} writeTag={writeTag} />);
expect(wrapper.find(".flowrate-select").hasClass("disabled")).toBeTruthy();
});
it("should disable set button on fluidlevel select button if parameter already selected", () => {
tags.sts_PID_Control.value = 1;
const wrapper = shallow(<Controls tags={tags} writeTag={writeTag} />);
expect(wrapper.find(".fluidlevel-select").hasClass("disabled")).toBeTruthy();
});
it("should disable set button on tubingpressure select button if parameter already selected", () => {
tags.sts_PID_Control.value = 2;
const wrapper = shallow(<Controls tags={tags} writeTag={writeTag} />);
expect(wrapper.find(".tubingpressure-select").hasClass("disabled")).toBeTruthy();
});
it("should disable set button on frequency select button if parameter already selected", () => {
tags.sts_PID_Control.value = 3;
const wrapper = shallow(<Controls tags={tags} writeTag={writeTag} />);
expect(wrapper.find(".frequency-select").hasClass("disabled")).toBeTruthy();
});
});
describe("control setpoints", () => {
let writeTag;
let wrapper;
beforeEach(() => {
writeTag = jest.fn((tag, val) => {
return {tag, val};
});
wrapper = shallow(<Controls tags={tags} writeTag={writeTag} />);
});
it("should update setpoint state on setpoint change", () => {
wrapper.find(".flowrate-input").simulate("change", {target: {value: 100.0 }});
expect(wrapper.state().setpoints.flowrate).toEqual(100.0);
wrapper.find(".fluidlevel-input").simulate("change", {target: {value: 200.0 }});
expect(wrapper.state().setpoints.fluidlevel).toEqual(200.0);
wrapper.find(".tubingpressure-input").simulate("change", {target: {value: 300.0 }});
expect(wrapper.state().setpoints.tubingpressure).toEqual(300.0);
wrapper.find(".frequency-input").simulate("change", {target: {value: 400.0 }});
expect(wrapper.state().setpoints.frequency).toEqual(400.0);
});
it("should call writeTag with the setpoint tag and the value", () => {
wrapper.find(".flowrate-input").simulate("change", {target: {value: 100.0 }});
wrapper.find(".flowrate-submit").simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toBeCalledWith("cfg_PID_FlowSP", 100.0);
wrapper.find(".fluidlevel-input").simulate("change", {target: {value: 200.0 }});
wrapper.find(".fluidlevel-submit").simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toBeCalledWith("cfg_PID_FluidLevelSP", 200.0);
wrapper.find(".tubingpressure-input").simulate("change", {target: {value: 300.0 }});
wrapper.find(".tubingpressure-submit").simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toBeCalledWith("cfg_PID_TubingPressureSP", 300.0);
wrapper.find(".frequency-input").simulate("change", {target: {value: 400.0 }});
wrapper.find(".frequency-submit").simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toBeCalledWith("cfg_PID_ManualSP", 400.0);
});
it("should call writeTag with cfg_PID_* and true when selecting control parameter", () => {
wrapper.find(".flowrate-select").simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toHaveBeenCalledWith("cfg_PID_Flow", true);
wrapper.find(".fluidlevel-select").simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toHaveBeenCalledWith("cfg_PID_FluidLevel", true);
wrapper.find(".tubingpressure-select").simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toHaveBeenCalledWith("cfg_PID_TubingPressure", true);
wrapper.find(".frequency-select").simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toHaveBeenCalledWith("cfg_PID_Manual", true);
});
});
it("should map state to props", () => {
expect(mapStateToProps({ tags: "tags", extra: "extra" })).toEqual({ tags: "tags" });
});
});

View File

@@ -0,0 +1,52 @@
import React from "react";
import { shallow, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
import { EventLog, mapStateToProps } from "../../app/src/components/EventLog";
describe("EventLog", () => {
const writeTag = jest.fn();
it("renders loading div if events not defined", () => {
const wrapper = shallow(<EventLog />);
expect(wrapper.find(".loading")).toHaveLength(1);
expect(wrapper.find(".loading").text()).toMatch("Loading");
});
it("should call writeTag with cmd_ResetAlarms and true on Reset button click", () => {
const wrapper = shallow(<EventLog events={[]} writeTag={writeTag} />);
wrapper.find(".reset-button").simulate("click", null);
expect(writeTag).toBeCalledWith("cmd_ResetAlarms", true);
});
describe("event timeline", () => {
let events, wrapper;
beforeEach(() => {
events = [
{ tag: "test", timestamp: new Date(), eventType: "alarm"},
{ tag: "tttt", timestamp: new Date(), eventType: "cmd" }
];
wrapper = shallow(<EventLog events={events} />);
});
it("should return a Timeline Event for each event", () => {
expect(wrapper.find("TimelineEvent")).toHaveLength(2);
});
it("shows a calendar icon for non-alarm events", () => {
expect(wrapper.find("TimelineEvent").last().html()).toMatch("calendar");
});
it("shows an exclamation triangle for alarm events", () => {
expect(wrapper.find("TimelineEvent").first().html()).toMatch("exclamation-triangle");
});
});
it("should map state to props", () => {
expect(mapStateToProps({ events: "events", extra: "extra" })).toEqual({ events: "events" });
});
});

View File

@@ -0,0 +1,89 @@
import React from "react";
import { shallow, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
import { Header, mapStateToProps } from "../../app/src/components/Header";
describe("Header", () => {
it("should display a nav item", () => {
const wrapper = shallow(<Header />);
expect(wrapper.find("nav")).toHaveLength(1);
});
describe("alarm button", () => {
it("does not display anything if props haven't been set", () => {
const wrapper = shallow(<Header alarms={undefined} />);
expect(wrapper.find(".no-alarm")).toHaveLength(1);
});
it("renders button for no alarms", () => {
const wrapper = shallow(<Header alarms={[]} />);
expect(wrapper.find(".alarm-button")).toHaveLength(1);
});
it("renders button for alarms", () => {
const wrapper = shallow(<Header alarms={["alarm_Test"]} />);
expect(wrapper.find(".alarm-button")).toHaveLength(1);
});
});
describe("running state indicator", () => {
const tags = {
Device_Status_INT: { name: "Device_Status_INT", value: 0 }
};
it("renders an empty span for no tags", () => {
const wrapper = shallow(<Header alarms={[]} tags={undefined} />);
expect(wrapper.find(".no-tags").text()).toEqual("");
});
it("renders and empty span for tags, but no Device_Status_INT", () => {
const wrapper = shallow(<Header alarms={[]} tags={[]} />);
expect(wrapper.find(".no-device-status").text()).toEqual("");
});
it("renders Running for value 0", () => {
const wrapper = shallow(<Header alarms={[]} tags={tags} />);
expect(wrapper.find(".status-indicator").text()).toMatch("Running");
});
it("renders Pumped Off for value 1", () => {
tags.Device_Status_INT.value = 1;
const wrapper = shallow(<Header alarms={[]} tags={tags} />);
expect(wrapper.find(".status-indicator").text()).toMatch("Pumped Off");
});
it("renders Alarmed for value 2", () => {
tags.Device_Status_INT.value = 2;
const wrapper = shallow(<Header alarms={[]} tags={tags} />);
expect(wrapper.find(".status-indicator").text()).toMatch("Alarmed");
});
it("renders Locked Out for value 3", () => {
tags.Device_Status_INT.value = 3;
const wrapper = shallow(<Header alarms={[]} tags={tags} />);
expect(wrapper.find(".status-indicator").text()).toMatch("Locked Out");
});
it("renders Stopped for value 4", () => {
tags.Device_Status_INT.value = 4;
const wrapper = shallow(<Header alarms={[]} tags={tags} />);
expect(wrapper.find(".status-indicator").text()).toMatch("Stopped");
});
it("renders Unknown for value 5", () => {
tags.Device_Status_INT.value = 5;
const wrapper = shallow(<Header alarms={[]} tags={tags} />);
expect(wrapper.find(".status-indicator").text()).toMatch("Unknown");
});
});
it("should map state to props", () => {
const state = { tags: "tags", alarms: "alarms", extra: "extra"};
expect(mapStateToProps(state)).toEqual({ tags: "tags", alarms: "alarms" });
});
});

View File

@@ -0,0 +1,83 @@
import React from "react";
import { shallow, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
import { Main, mapStateToProps } from "../../app/src/components/Main";
describe("Main", () => {
const tags = {
val_FluidLevel: { name: "val_FluidLevel", value: 100.0 },
val_Flowmeter_BarrelsPerDay: { name: "val_Flowmeter_BarrelsPerDay", value: 200.0 },
val_TubingPressure: { name: "val_TubingPressure", value: 150.0 },
VFD_OutCurrent: { name: "VFD_OutCurrent", value: 30.0 },
VFD_SpeedFdbk: { name: "VFD_SpeedFdbk", value: 60.0 }
};
const tagHistory = {
val_FluidLevel: [{ timestamp: 0, value: 100.0 }],
val_Flowmeter_BarrelsPerDay: [{ timestamp: 0, value: 200.0 }],
val_TubingPressure: [{ timestamp: 0, value: 150.0 }],
VFD_OutCurrent: [{ timestamp: 0, value: 30.0 }],
VFD_SpeedFdbk: [{ timestamp: 0, value: 60.0 }]
};
it("should render loading div if no tag history", () =>{
const wrapper = shallow(<Main tagHistory={undefined} />);
expect(wrapper.find(".loading").text()).toMatch("Loading");
});
it("should render plc error class if PLC error", () => {
const wrapper = shallow(<Main tagHistory={{}} plc={{ error: true }} />);
expect(wrapper.find(".plc-error").text()).toMatch("PLC Error");
});
it("should render waiting if no tags yet", () => {
const wrapper = shallow(<Main tagHistory={{}} plc={{}} tags={{}} />);
expect(wrapper.find(".waiting").text()).toMatch("Waiting for data");
});
it("should render the main container if everything ready", () => {
const wrapper = shallow(<Main tagHistory={tagHistory} plc={{}} tags={tags} />);
expect(wrapper.find(".main")).toHaveLength(1);
});
describe("tag current values", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<Main tagHistory={tagHistory} plc={{}} tags={tags} />);
});
it("should render a liquid fill gauge for all gauges", () => {
expect(wrapper.find(".liquidfill")).toHaveLength(5);
});
it("should render a liquidfill gauge for val_FluidLevel", () => {
expect(wrapper.find(".lqdfill-val_FluidLevel")).toHaveLength(1);
});
it("should render a liquidfill gauge for val_Flowmeter_BarrelsPerDay", () => {
expect(wrapper.find(".lqdfill-val_Flowmeter_BarrelsPerDay")).toHaveLength(1);
});
it("should render a liquidfill gauge for val_TubingPressure", () => {
expect(wrapper.find(".lqdfill-val_TubingPressure")).toHaveLength(1);
});
it("should render a liquidfill gauge for VFD_OutCurrent", () => {
expect(wrapper.find(".lqdfill-VFD_OutCurrent")).toHaveLength(1);
});
it("should render a liquidfill gauge for VFD_SpeedFdbk", () => {
expect(wrapper.find(".lqdfill-VFD_SpeedFdbk")).toHaveLength(1);
});
});
it("should map state to props", () => {
expect(mapStateToProps({ tags: "tags", plc: "plc", tagHistory: "tagHistory", extra: "extra" })).toEqual({ tags: "tags", plc: "plc", tagHistory: "tagHistory" });
});
});

View File

@@ -0,0 +1,53 @@
import React from "react";
import { shallow, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
import { Permissives, mapStateToProps } from "../../app/src/components/Permissives";
describe("Permissives", () => {
it("should render a loading page if no tags", () => {
const wrapper = shallow(<Permissives tags={undefined} />);
expect(wrapper.find(".loading").text()).toMatch("Loading");
});
it("should render the permissives page", () => {
const wrapper = shallow(<Permissives tags={[]} />);
expect(wrapper.find(".permissives")).toHaveLength(1);
});
it("should render green button for permissive true", () => {
const tags = {
sp_ALL: { value: true },
rp_ALL: { value: true },
sp_Flowmeter: { value: true },
rp_Flowmeter: { value: true },
sp_IntakePressure: { value: true },
rp_IntakePressure: { value: true },
sp_IntakeTemperature: { value: true },
rp_IntakeTemperature: { value: true }
};
const wrapper = shallow(<Permissives tags={tags} />);
expect(wrapper.find(".btn-success")).toHaveLength(8);
});
it("should render red button for permissive false", () => {
const tags = {
sp_ALL: { value: false },
rp_ALL: { value: false },
sp_Flowmeter: { value: false },
rp_Flowmeter: { value: false },
sp_IntakePressure: { value: false },
rp_IntakePressure: { value: false },
sp_IntakeTemperature: { value: false },
rp_IntakeTemperature: { value: false }
};
const wrapper = shallow(<Permissives tags={tags} />);
expect(wrapper.find(".btn-danger")).toHaveLength(8);
});
it("should map state to props correctly", () => {
const state = { tags: "tags", extra: "extra" };
expect(mapStateToProps(state)).toEqual({ tags: "tags" });
});
});

View File

@@ -0,0 +1,109 @@
import React from "react";
import { shallow, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
import { Settings, mapStateToProps } from "../../app/src/components/Settings";
describe("Settings", () => {
let container;
const testTagList = [
{
name: "test1",
value: 100
},
{
name: "test2",
value: 200
}
];
const plcTestData = {
details: {
name: "test PLC"
},
ipAddress: "192.168.1.10"
};
const setPlcIpAddress = jest.fn((ipAddress) => ipAddress);
const storeNewTag = jest.fn((tagName) => tagName);
const deleteTag = jest.fn((tagName) => tagName);
const ipcPlcInitializeSend = jest.fn((addr, tagList) => {
return {addr, tagList};
});
beforeEach(() => {
container = shallow(
<Settings
plc={plcTestData}
tags={testTagList}
setPlcIpAddress={setPlcIpAddress}
storeNewTag={storeNewTag}
deleteTag={deleteTag}
ipcPlcInitializeSend={ipcPlcInitializeSend}
history={[]}
/>);
});
it("should display a settings item", () => {
expect(container.find(".settings")).toHaveLength(1);
});
describe("ip address field", () => {
it("should update state when ip address changed", () => {
container.find(".ip-address-field").simulate("change", {target: {value: "1.1.1.1"}});
expect(container.state().ipAddress).toEqual("1.1.1.1");
});
it("should run the setPlcIpaddress function when update clicked", () =>{
container.find(".ip-address-field").simulate("change", {target: {value: "1.1.1.1"}});
container.find(".ip-submit-button").simulate("click", {preventDefault: jest.fn()});
expect(setPlcIpAddress).toHaveBeenCalled();
});
it("should not allow setting ip address if ip address not changed", () => {
expect(container.find(".ip-submit-button").hasClass("disabled")).toBeTruthy();
container.find(".ip-address-field").simulate("change", {target: {value: "1.1.1.1"}});
expect(container.find(".ip-submit-button").hasClass("disabled")).toBeFalsy();
});
});
describe("tag list", () => {
it("should have one row for each tag", () => {
expect(container.find(".tag-list li")).toHaveLength(2);
});
it("should update state when new tag input changed", () => {
container.find(".tag-name-input").simulate("change", {target: {value: "testtag"}});
expect(container.state().newTag).toEqual("testtag");
});
it("should run the storeNewTag function on submit button click", () => {
container.find(".tag-name-input").simulate("change", {target: {value: "testtag"}});
container.find(".add-tag-button").simulate("click", {preventDefault: jest.fn()});
expect(storeNewTag).toHaveBeenCalled();
});
it("should run the ipcPlcInitializeSend function on save button click", () => {
container.find(".ip-address-field").simulate("change", {target: {value: "1.1.1.1"}});
container.find(".ip-submit-button").simulate("click", {preventDefault: jest.fn()});
container.find(".tag-name-input").simulate("change", {target: {value: "testtag"}});
container.find(".add-tag-button").simulate("click", {preventDefault: jest.fn()});
container.find(".save-button").simulate("click", {preventDefault: jest.fn()});
expect(ipcPlcInitializeSend).toHaveBeenCalled();
});
it("should run the deleteTag function on delete button click", () => {
container.find(".delete-button").first().simulate("click", {preventDefault: jest.fn()});
expect(deleteTag).toHaveBeenCalled();
});
});
it("should map state to props", () => {
expect(mapStateToProps({ tags: "tags", plc: "plc", extra: "extra" })).toEqual({ tags: "tags", plc: "plc" });
});
});

View File

@@ -0,0 +1,75 @@
import React from "react";
import { shallow, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
import { TagsIndex, mapStateToProps } from "../../app/src/components/TagsIndex";
describe("TagsIndex", () => {
let container;
beforeEach(() => {
null;
});
it("should display a div for no tags found", () => {
container = shallow(<TagsIndex plc={{}} tags={[]} />);
expect(container.find(".tags-index-notags")).toHaveLength(1);
});
describe("when tags are found", () => {
const testTagList = [
{
name: "test1",
value: 100
},
{
name: "test2",
value: 200
}
];
const plcTestData = {
details: {
name: "test PLC"
},
ipAddress: "192.168.1.10"
};
const writeTag = jest.fn();
beforeEach(() => {
container = shallow(<TagsIndex plc={plcTestData} tags={testTagList} writeTag={writeTag} />);
});
it("should display a div for tags found", () => {
expect(container.find(".tags-index-withtags")).toHaveLength(1);
});
it("should render 1 row for each tag", () => {
expect(container.find(".tags-index-withtags table tbody tr")).toHaveLength(2);
});
it("should display a plc IP address", () => {
expect(container.find(".tags-index-withtags h2").text()).toMatch("192.168.1.10");
});
it("should update state on changing write value", () => {
container.find(".tag-write-input").first().simulate("change", {target: {value: 111.1}});
expect(container.state().writes["test1"]).toEqual(111.1);
});
it("should run the writeTag function when clicking Write", () => {
container.find(".tag-write-input").first().simulate("change", {target: {value: 111.1}});
container.find(".tag-write-button").first().simulate("click", {preventDefault: jest.fn()});
expect(writeTag).toHaveBeenCalled();
});
});
it("should map state to props", () => {
expect(mapStateToProps({ tags: "tags", plc: "plc", extra: "extra" })).toEqual({ tags: "tags", plc: "plc" });
});
});

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@ import { connect } from "react-redux";
import { writeTag } from "../actions/actions_tags";
class Controls extends Component {
export class Controls extends Component {
constructor(props){
super(props);
@@ -50,7 +50,7 @@ class Controls extends Component {
}
onPIDParameterSelect = (e, parName) => {
onPIDParameterSelect = (event, parName) => {
event.preventDefault();
switch(parName){
@@ -90,7 +90,7 @@ class Controls extends Component {
render(){
if (!this.props.tags){
return(
<div>
<div className="loading-notags">
<h1>Loading...</h1>
</div>
);
@@ -98,17 +98,17 @@ class Controls extends Component {
if (!this.props.tags.Device_Status_INT){
return(
<div>
<div className="loading-nostatus">
<h1>Loading...</h1>
</div>
);
}
const startBtnClass = this.props.tags.Device_Status_INT.value === 4 ? "btn btn-success control-button" : "btn btn-secondary disabled control-button";
const stopBtnClass = this.props.tags.Device_Status_INT.value !== 4 ? "btn btn-danger control-button" : "btn btn-secondary disabled control-button";
const startBtnClass = this.props.tags.Device_Status_INT.value === 4 ? "btn btn-success control-button start-button" : "btn btn-secondary disabled control-button start-button";
const stopBtnClass = this.props.tags.Device_Status_INT.value !== 4 ? "btn btn-danger control-button stop-button" : "btn btn-secondary disabled control-button stop-button";
return (
<div className="container" style={{textAlign: "center"}}>
<div className="container controls" style={{textAlign: "center"}}>
<h1>Controls</h1>
<table className="table">
<tbody>
@@ -138,7 +138,7 @@ class Controls extends Component {
<tr>
<td>
<button
className={this.props.tags.sts_PID_Control.value === 0 ? "btn btn-success disabled" : "btn btn-light"}
className={this.props.tags.sts_PID_Control.value === 0 ? "btn btn-success disabled flowrate-select" : "btn btn-light flowrate-select"}
onClick={(e) => this.onPIDParameterSelect(e, "flowrate")}
>
Flow Rate
@@ -146,7 +146,7 @@ class Controls extends Component {
</td>
<td>
<button
className={this.props.tags.sts_PID_Control.value === 1 ? "btn btn-success disabled" : "btn btn-light"}
className={this.props.tags.sts_PID_Control.value === 1 ? "btn btn-success disabled fluidlevel-select" : "btn btn-light fluidlevel-select"}
onClick={(e) => this.onPIDParameterSelect(e, "fluidlevel")}
>
Fluid Level
@@ -154,7 +154,7 @@ class Controls extends Component {
</td>
<td>
<button
className={this.props.tags.sts_PID_Control.value === 2 ? "btn btn-success disabled" : "btn btn-light"}
className={this.props.tags.sts_PID_Control.value === 2 ? "btn btn-success disabled tubingpressure-select" : "btn btn-light tubingpressure-select"}
onClick={(e) => this.onPIDParameterSelect(e, "tubingpressure")}
>
Tubing Pressure
@@ -162,7 +162,7 @@ class Controls extends Component {
</td>
<td>
<button
className={this.props.tags.sts_PID_Control.value === 3 ? "btn btn-success disabled" : "btn btn-light"}
className={this.props.tags.sts_PID_Control.value === 3 ? "btn btn-success disabled frequency-select" : "btn btn-light frequency-select"}
onClick={(e) => this.onPIDParameterSelect(e, "frequency")}
>
Manual Frequency
@@ -175,7 +175,7 @@ class Controls extends Component {
<input
value={this.state.setpoints.flowrate}
onChange={(e) => this.onSetpointChange(e, "flowrate")}
className="form-control"
className="form-control flowrate-input"
type="number"
/>
</td>
@@ -183,7 +183,7 @@ class Controls extends Component {
<input
value={this.state.setpoints.fluidlevel}
onChange={(e) => this.onSetpointChange(e, "fluidlevel")}
className="form-control"
className="form-control fluidlevel-input"
type="number"
/>
</td>
@@ -191,7 +191,7 @@ class Controls extends Component {
<input
value={this.state.setpoints.tubingpressure}
onChange={(e) => this.onSetpointChange(e, "tubingpressure")}
className="form-control"
className="form-control tubingpressure-input"
type="number"
/>
</td>
@@ -199,7 +199,7 @@ class Controls extends Component {
<input
value={this.state.setpoints.frequency}
onChange={(e) => this.onSetpointChange(e, "frequency")}
className="form-control"
className="form-control frequency-input"
type="number"
/>
</td>
@@ -209,7 +209,7 @@ class Controls extends Component {
<td>
<button
onClick={(e) => this.onSetpointSubmit(e, "flowrate")}
className="btn btn-primary"
className="btn btn-primary flowrate-submit"
>
Update
</button>
@@ -218,7 +218,7 @@ class Controls extends Component {
<td>
<button
onClick={(e) => this.onSetpointSubmit(e, "fluidlevel")}
className="btn btn-primary"
className="btn btn-primary fluidlevel-submit"
>
Update
</button>
@@ -227,7 +227,7 @@ class Controls extends Component {
<td>
<button
onClick={(e) => this.onSetpointSubmit(e, "tubingpressure")}
className="btn btn-primary"
className="btn btn-primary tubingpressure-submit"
>
Update
</button>
@@ -236,7 +236,7 @@ class Controls extends Component {
<td>
<button
onClick={(e) => this.onSetpointSubmit(e, "frequency")}
className="btn btn-primary"
className="btn btn-primary frequency-submit"
>
Update
</button>
@@ -258,7 +258,7 @@ class Controls extends Component {
*
* @returns {Object} mapped state
*/
function mapStateToProps(state){
export function mapStateToProps(state){
return {
tags: state.tags
};

View File

@@ -7,7 +7,7 @@ import { faExclamationTriangle, faCalendar } from "@fortawesome/fontawesome-pro-
import { writeTag } from "../actions/actions_tags";
class EventLog extends Component {
export class EventLog extends Component {
renderTimelineEvents(){
return _.map(this.props.events, (event, key) => {
@@ -38,7 +38,7 @@ class EventLog extends Component {
render(){
if (!this.props.events){
return(
<div>
<div className="loading">
<h1>Loading...</h1>
</div>
);
@@ -46,7 +46,7 @@ class EventLog extends Component {
return (
<div className="container">
<button
className="btn btn-primary"
className="btn btn-primary reset-button"
onClick={() => this.props.writeTag("cmd_ResetAlarms", true)}
style={{marginTop: "10px", marginBottom: "10px"}}
>
@@ -66,7 +66,7 @@ class EventLog extends Component {
*
* @returns {Object} mapped state
*/
function mapStateToProps(state){
export function mapStateToProps(state){
return {
events: state.events
};

View File

@@ -8,7 +8,7 @@ import { faTint, faCog } from "@fortawesome/fontawesome-pro-regular";
*
* @extends React.Component
*/
class Header extends Component {
export class Header extends Component {
/**
@@ -18,11 +18,11 @@ class Header extends Component {
*/
renderRunningState(){
if(!this.props.tags ){
return <span></span>;
return <span className="no-tags"></span>;
}
if (!this.props.tags.Device_Status_INT){
return <span></span>;
return <span className="no-device-status"></span>;
}
let deviceStatus;
@@ -53,7 +53,7 @@ class Header extends Component {
deviceClass = "btn btn-info";
}
return <button className={deviceClass + " disabled"}>{deviceStatus}</button>;
return <button className={deviceClass + " disabled status-indicator"}>{deviceStatus}</button>;
}
@@ -64,10 +64,10 @@ class Header extends Component {
*/
renderAlarmButton(){
if (!this.props.alarms){
return <span></span>;
return <span className="no-alarm"></span>;
}
const btnClassName = this.props.alarms.length > 0 ? "btn btn-danger alarms-active" : "btn btn-light";
const btnClassName = this.props.alarms.length > 0 ? "btn btn-danger alarms-active alarm-button" : "btn btn-light alarm-button";
const btnText = this.props.alarms.length > 0 ? `Alarms Active: ${this.props.alarms.length}` : "No Alarms";
return (
@@ -115,7 +115,7 @@ class Header extends Component {
*
* @returns {Object} mapped state
*/
function mapStateToProps(state){
export function mapStateToProps(state){
return {
tags: state.tags,
alarms: state.alarms

View File

@@ -2,18 +2,17 @@ import React, { Component } from "react";
import _ from "lodash";
import { connect } from "react-redux";
import { FlexibleWidthXYPlot, LineSeries, VerticalGridLines, HorizontalGridLines, XAxis, YAxis, DiscreteColorLegend } from "react-vis";
// import "../../../node_modules/react-vis/dist/style.css";
import "../../../node_modules/react-vis/dist/style.css";
import { color } from "d3-color";
import { interpolateRgb } from "d3-interpolate";
import LiquidFillGauge from "react-liquid-gauge";
import { RadialGauge } from "react-canvas-gauges";
/** Class for Main Page
*
* @extends React.Component
*/
class Main extends Component {
export class Main extends Component {
/**
* Map an array of objects to a graph-usable array of objects
@@ -68,9 +67,9 @@ class Main extends Component {
offset: "100%"
}
];
return (
<div className="col" style={{textAlign: "center"}}>
<div className={"col liquidfill lqdfill-" + tag.name} style={{textAlign: "center"}}>
<h3>{label}</h3>
<LiquidFillGauge
style={{ margin: "0 auto" }}
@@ -124,39 +123,6 @@ class Main extends Component {
);
}
/**
* Renders a radial gauge.
*
* @param {Object} tag - the tag structure
* @param {string} units - units for the tag
* @param {number} maxValue - maximum value to be displayed
* @param {number} label - label for the value (typically the tag name)
*/
renderRadialGauge(tag, units, maxValue, label){
if (!tag){
return <span></span>;
}
const ticks = [ 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 ];
return (
<div className="col" style={{textAlign: "center"}}>
<h3>{label}</h3>
<RadialGauge
height={200}
width={200}
units={units}
title={label}
value={tag.value}
minValue={0}
maxValue={maxValue}
majorTicks={ticks.map((t) => t * maxValue)}
minorTicks={2}
></RadialGauge>
</div>
);
}
/**
* render
@@ -166,7 +132,7 @@ class Main extends Component {
render(){
if (!this.props.tagHistory){
return(
<div className="container">
<div className="container loading">
<h1>Loading...</h1>
</div>
);
@@ -183,7 +149,7 @@ class Main extends Component {
if (Object.keys(this.props.tags).length === 0){
return(
<div className="container">
<div className="container waiting">
<h1>Waiting for data...</h1>
</div>
);
@@ -191,12 +157,12 @@ class Main extends Component {
return (
<div className="container">
<div className="container main">
<h3>Process Values</h3>
<div className="row" style={{marginBottom: "20px"}}>
{this.renderLiquidGauge(this.props.tags.val_FluidLevel, "ft.", 500, "Level")}
{this.renderRadialGauge(this.props.tags.val_Flowmeter_BarrelsPerDay, "BPD", 5000, "Flow Rate")}
{this.renderRadialGauge(this.props.tags.val_TubingPressure, "PSI", 400, "Tubing Pressure")}
{this.renderLiquidGauge(this.props.tags.val_Flowmeter_BarrelsPerDay, "BPD", 5000, "Flow Rate")}
{this.renderLiquidGauge(this.props.tags.val_TubingPressure, "PSI", 400, "Tubing Pressure")}
</div>
<FlexibleWidthXYPlot
@@ -207,21 +173,11 @@ class Main extends Component {
<HorizontalGridLines />
<XAxis />
<YAxis />
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_IntakePressure)}
/>
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_Flowmeter)}
/>
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_FluidLevel)}
/>
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_IntakeTemperature)}
/>
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_TubingPressure)}
/>
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_IntakePressure)} />
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_Flowmeter)} />
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_FluidLevel)} />
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_IntakeTemperature)} />
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_TubingPressure)} />
</FlexibleWidthXYPlot>
<DiscreteColorLegend
items={[
@@ -237,8 +193,8 @@ class Main extends Component {
<h3>VFD Data</h3>
<div className="row" style={{marginBottom: "20px"}}>
{this.renderRadialGauge(this.props.tags.VFD_OutCurrent, "A.", 100, "Current")}
{this.renderRadialGauge(this.props.tags.VFD_SpeedFdbk, "Hz", 60, "Frequency")}
{this.renderLiquidGauge(this.props.tags.VFD_OutCurrent, "A.", 100, "Current")}
{this.renderLiquidGauge(this.props.tags.VFD_SpeedFdbk, "Hz", 60, "Frequency")}
</div>
<FlexibleWidthXYPlot
height={300}
@@ -248,18 +204,10 @@ class Main extends Component {
<HorizontalGridLines />
<XAxis />
<YAxis />
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_OutCurrent)}
/>
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_SpeedFdbk)}
/>
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_OutPower)}
/>
<LineSeries
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_Temp)}
/>
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_OutCurrent)} />
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_SpeedFdbk)} />
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_OutPower)} />
<LineSeries data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_Temp)} />
</FlexibleWidthXYPlot>
<DiscreteColorLegend
items={[
@@ -283,7 +231,7 @@ class Main extends Component {
*
* @returns {Object} mapped state
*/
function mapStateToProps(state){
export function mapStateToProps(state){
return {
tags: state.tags,
tagHistory: state.tagHistory,

View File

@@ -1,7 +1,7 @@
import React, { Component } from "react";
import { connect } from "react-redux";
class Permissives extends Component {
export class Permissives extends Component {
renderTagButton = (tagName, description) => {
if (!this.props.tags[tagName]){
@@ -21,14 +21,14 @@ class Permissives extends Component {
render(){
if (!this.props.tags){
return (
<div>
<div className="loading">
<h2>Loading...</h2>
</div>
);
}
return(
<div>
<div className="permissives">
<table id="permissives-table" className="table">
<thead>
<tr>
@@ -72,7 +72,7 @@ class Permissives extends Component {
*
* @returns {Object} mapped state
*/
function mapStateToProps(state){
export function mapStateToProps(state){
return {
tags: state.tags
};

View File

@@ -6,7 +6,7 @@ import { storeNewTag, deleteTag } from "../actions/actions_tags";
import FontAwesomeIcon from "@fortawesome/react-fontawesome";
import { faTimesSquare } from "@fortawesome/fontawesome-pro-regular";
class Settings extends Component {
export class Settings extends Component {
constructor(props){
super(props);
@@ -24,7 +24,7 @@ class Settings extends Component {
<li key={tag.name} className="list-group-item">
{tag.name}
<button
className="btn btn-outline-danger float-right"
className="btn btn-outline-danger float-right delete-button"
onClick={(e) => this.onDeleteClick(e, tag.name)}
>
<FontAwesomeIcon icon={faTimesSquare} />
@@ -35,9 +35,7 @@ class Settings extends Component {
}
componentWillMount(){
console.log(this.props.plc);
if (this.props.plc && this.props.plc.ipAddress){
console.log(this.props.plc.ipAddress);
this.setState({ipAddress: this.props.plc.ipAddress});
}
}
@@ -73,24 +71,24 @@ class Settings extends Component {
}
render() {
const ipAddressBtnClass = ((this.state.ipAddress === this.props.plc.ipAddress) || (this.state.ipAddress.length === 0)) ? "btn btn-primary disabled" : "btn btn-primary";
const initializeBtnClass = (this.props.plc.ipAddress && _.map(this.props.tags, (t)=>t).length > 0) ? "btn btn-success" : "btn sbtn-success disabled";
const ipAddressBtnClass = ((this.state.ipAddress === this.props.plc.ipAddress) || (this.state.ipAddress.length === 0)) ? "disabled" : "";
const initializeBtnClass = (this.props.plc.ipAddress && _.map(this.props.tags, (t)=>t).length > 0) ? "" : "disabled";
return (
<div className="container">
<div className="container settings">
<h1>Settings</h1>
<form className="form-inline mb-2">
<div className="form-group">
<label htmlFor="ipAddress">IP Address</label>
<input
id="ipAddress"
className="form-control "
className="form-control ip-address-field"
value={this.state.ipAddress}
placeholder="PLC IP Address"
onChange={this.onIpAddressInputChange}
/>
<button
className={ipAddressBtnClass}
className={ipAddressBtnClass + " btn btn-primary ip-submit-button"}
onClick={(e) => this.sendIpAddress(e)}>
Set IP Address
</button>
@@ -99,7 +97,7 @@ class Settings extends Component {
<h4>Tag List</h4>
<ul className="list-group">
<ul className="list-group tag-list">
{this.getTagList()}
</ul>
<form>
@@ -107,14 +105,15 @@ class Settings extends Component {
value={this.state.newTag}
onChange={this.onNewTagChange}
placeholder="New Tag Name..."
className="tag-name-input"
/>
<button
className="btn btn-success float-right"
className="btn btn-success float-right add-tag-button"
onClick={(e) => this.onNewTagSubmit(e)}
>Add Tag</button>
<button
className={initializeBtnClass}
className={initializeBtnClass + " btn btn-success save-button"}
onClick={(e) => this.onSave(e)}
>Save</button>
</form>
@@ -131,7 +130,7 @@ class Settings extends Component {
*
* @returns {Object} mapped state
*/
function mapStateToProps(state){
export function mapStateToProps(state){
return{
tags: state.tags,
plc: state.plc

View File

@@ -2,13 +2,12 @@ import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import _ from "lodash";
import Gauge from "react-svg-gauge";
import { writeTag } from "../actions/actions_tags";
class TagsIndex extends Component {
export class TagsIndex extends Component {
constructor(props){
super(props);
@@ -22,36 +21,21 @@ class TagsIndex extends Component {
<td>{Math.round(t.value * 100) / 100}</td>
<td><input
onChange={(e) => this.onTagWriteFieldChanged(e, t.name)}
className="tag-write-input"
/></td>
<td><button
className="waves-effect waves-light btn"
className="waves-effect waves-light btn tag-write-button"
onClick={() => this.onWriteButtonClick(t.name)}
>Write</button></td>
</tr>);
});
}
renderTagsGauges(){
return _.map(this.props.tags, (tag) => {
return (<div className="col s4" key={tag.name}>
<Gauge value={Math.round(tag.value * 100) / 100}
width={200}
height={150}
label={tag.name}
valueLabelStyle={styles.valueLabel}
topLabelStyle={styles.topLabel}
minMaxLabelStyle={styles.minMaxLabel} />
</div>);
});
}
onWriteButtonClick = (tagName) => {
console.log(tagName, this.state.writes[tagName]);
this.props.writeTag(tagName, this.state.writes[tagName]);
}
onTagWriteFieldChanged = (e, tagName) => {
console.log(tagName, e.target.value);
this.setState({writes: {...this.state.writes, [tagName]: e.target.value}});
}
@@ -63,7 +47,9 @@ class TagsIndex extends Component {
if (!this.props.tags || _.map(this.props.tags, (t)=> t ).length === 0) {
return (
<div style={styles.container}>
<div style={styles.container}
className="tags-index-notags"
>
<h3>
No Tags.
</h3>
@@ -73,7 +59,7 @@ class TagsIndex extends Component {
}
return (
<div style={styles.container}>
<div style={styles.container} className="tags-index-withtags">
<h2>Tags for {this.props.plc.ipAddress}</h2>
<h3>{PLC}</h3>
<table className="table">
@@ -127,7 +113,7 @@ const styles = {
*
* @returns {Object} mapped state
*/
function mapStateToProps(state){
export function mapStateToProps(state){
return {
tags: state.tags,
plc: state.plc

View File

@@ -19,7 +19,8 @@
"node_modules/(?!(electron)/)"
],
"moduleNameMapper": {
"electron": "<rootDir>/__mock__/electron.js"
"electron": "<rootDir>/__mock__/electron.js",
"\\.(css|less)$": "identity-obj-proxy"
},
"testEnvironment": "node"
},
@@ -37,16 +38,17 @@
"electron": "^2.0.0-beta.5",
"electron-builder": "^20.8.1",
"electron-reload": "^1.2.2",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "^4.19.1",
"eslint-plugin-react": "^7.7.0",
"file-loader": "^1.1.10",
"identity-obj-proxy": "^3.0.0",
"jest": "^22.4.3",
"mini-css-extract-plugin": "^0.4.0",
"npm-run-all": "^4.1.2",
"webpack": "^4.1.1",
"webpack-cli": "^2.0.11",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1"
"webpack-cli": "^2.0.11"
},
"dependencies": {
"@fortawesome/fontawesome": "^1.1.5",
@@ -59,14 +61,12 @@
"ethernet-ip": "^1.1.4",
"lodash": "^4.17.5",
"react": "^16.2.0",
"react-canvas-gauges": "^1.2.1",
"react-dom": "^16.2.0",
"react-event-timeline": "^1.5.1",
"react-liquid-gauge": "^1.2.4",
"react-redux": "^5.0.7",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-svg-gauge": "^1.0.7",
"react-vis": "^1.9.2",
"redux": "^3.7.2",
"redux-electron-ipc": "^1.1.12",

8841
yarn.lock Normal file

File diff suppressed because it is too large Load Diff