diff --git a/frontend/src/devicelist/DeviceListPage.jsx b/frontend/src/devicelist/DeviceListPage.jsx index 850c2df19..37f95c59e 100644 --- a/frontend/src/devicelist/DeviceListPage.jsx +++ b/frontend/src/devicelist/DeviceListPage.jsx @@ -1,9 +1,7 @@ import React from "react"; import Page from "../general/Page"; -import DeviceListTabTable from "./DeviceListTabTable"; -import * as useStyles from "../DefaultMakeStylesTheme"; -import * as dataSource from "../api/DeviceApi"; +import DeviceListPageContents from "./DeviceListPageContents"; export default function DeviceListPage() { const breadcrumb = [ @@ -12,7 +10,7 @@ export default function DeviceListPage() { ]; return ( - + ); } diff --git a/frontend/src/devicelist/DeviceListPageContents.jsx b/frontend/src/devicelist/DeviceListPageContents.jsx new file mode 100644 index 000000000..461eec716 --- /dev/null +++ b/frontend/src/devicelist/DeviceListPageContents.jsx @@ -0,0 +1,66 @@ +import React from "react"; + +import { getSenders, getReceivers } from "../api/DeviceApi"; +import DeviceTableTitle from "./DeviceTableTitle"; +import DevicesTable from "./DevicesTable"; + +export default class DeviceListPageContents extends React.Component { + constructor(props) { + super(props); + this.state = { + selected: 0, + senders: [], + receivers: [] + }; + + this.handleChange = this.handleChange.bind(this); + this.handleSendersChange = this.handleSendersChange.bind(this); + this.handleReceiversChange = this.handleReceiversChange.bind(this); + } + + componentDidMount() { + getSenders(this.handleSendersChange); + getReceivers(this.handleReceiversChange); + } + + handleChange(value) { + this.setState({ + selected: value + }); + } + + handleSendersChange(senders) { + this.setState({ + senders + }); + } + + handleReceiversChange(receivers) { + this.setState({ + receivers + }); + } + + getDevices() { + const { receivers, senders, selected } = this.state; + switch (selected) { + case 1: + return senders; + case 2: + return receivers; + default: + return senders.concat(receivers); + } + } + + getTitle() { + const { selected } = this.state; + return ( + + ); + } + + render() { + return ; + } +} diff --git a/frontend/src/devicelist/DeviceListTabTable.jsx b/frontend/src/devicelist/DeviceListTabTable.jsx deleted file mode 100644 index c83953205..000000000 --- a/frontend/src/devicelist/DeviceListTabTable.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { Box } from "@material-ui/core"; - -import TabPanel from "./TabPanel"; -import DevicesTable from "./DevicesTable"; -import VerticalTabs from "../general/VerticalTabs"; - -export default class DeviceListTabTable extends React.Component { - constructor(props) { - super(props); - this.state = { - senders: [], - receivers: [], - value: 0 - }; - this.handleValueChange = this.handleValueChange.bind(this); - this.handleSendersChange = this.handleSendersChange.bind(this); - this.handleReceiversChange = this.handleReceiversChange.bind(this); - } - - componentDidMount() { - const { dataSource } = this.props; - dataSource.getSenders(this.handleSendersChange); - dataSource.getReceivers(this.handleReceiversChange); - } - - handleValueChange(tabIndex) { - this.setState({ - value: tabIndex - }); - } - - handleSendersChange(senders) { - this.setState({ - senders - }); - } - - handleReceiversChange(receivers) { - this.setState({ - receivers - }); - } - - render() { - const { receivers, senders, value } = this.state; - const { classes } = this.props; - return ( - <> - - - - - - - - - - - ); - } -} -DeviceListTabTable.propTypes = { - dataSource: PropTypes.objectOf(PropTypes.func).isRequired, - classes: PropTypes.shape({ - tabs: PropTypes.string - }).isRequired -}; diff --git a/frontend/src/devicelist/DeviceTableTitle.jsx b/frontend/src/devicelist/DeviceTableTitle.jsx new file mode 100644 index 000000000..2dbc697e8 --- /dev/null +++ b/frontend/src/devicelist/DeviceTableTitle.jsx @@ -0,0 +1,38 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { MenuItem, Select } from "@material-ui/core"; +import StyledInput from "./StyledInput"; + +export default class DeviceTableTitle extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + } + + handleChange(event) { + const { handleChange } = this.props; + handleChange(event.target.value); + } + + render() { + const { index } = this.props; + return ( + + ); + } +} + +DeviceTableTitle.propTypes = { + index: PropTypes.number.isRequired, + handleChange: PropTypes.func.isRequired +}; diff --git a/frontend/src/devicelist/DevicesTable.jsx b/frontend/src/devicelist/DevicesTable.jsx index ef3126b77..6a96de58c 100644 --- a/frontend/src/devicelist/DevicesTable.jsx +++ b/frontend/src/devicelist/DevicesTable.jsx @@ -149,6 +149,6 @@ export default function DevicesTable(props) { } DevicesTable.propTypes = { - title: PropTypes.string.isRequired, + title: PropTypes.node.isRequired, devices: PropTypes.arrayOf(PropTypes.instanceOf(DeviceInfo)).isRequired }; diff --git a/frontend/src/devicelist/StyledInput.jsx b/frontend/src/devicelist/StyledInput.jsx new file mode 100644 index 000000000..67b4a0cfa --- /dev/null +++ b/frontend/src/devicelist/StyledInput.jsx @@ -0,0 +1,18 @@ +import { InputBase, withStyles } from "@material-ui/core"; + +export default withStyles({ + input: { + borderRadius: 4, + position: "relative", + fontWeight: 500, + letterSpacing: "0.0075em", + lineHeight: "1.6", + fontSize: "1.25rem", + padding: "10px 26px 10px 12px", + "&:focus": { + borderRadius: 4, + borderColor: "#80bdff", + boxShadow: "0 0 0 0.2rem rgba(0,123,255,.25)" + } + } +})(InputBase); diff --git a/frontend/src/devicelist/TabPanel.jsx b/frontend/src/devicelist/TabPanel.jsx deleted file mode 100644 index 6ef28b085..000000000 --- a/frontend/src/devicelist/TabPanel.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; -import { Box, Typography } from "@material-ui/core"; -import PropTypes from "prop-types"; - -export default function TabPanel(props) { - const { children, value, index } = props; - - return ( - - ); -} -TabPanel.propTypes = { - children: PropTypes.element.isRequired, - value: PropTypes.number.isRequired, - index: PropTypes.number.isRequired -}; diff --git a/frontend/src/devicelist/__tests__/DeviceListPage.test.jsx b/frontend/src/devicelist/__tests__/DeviceListPage.test.jsx index e09bc3552..af6e7ea32 100644 --- a/frontend/src/devicelist/__tests__/DeviceListPage.test.jsx +++ b/frontend/src/devicelist/__tests__/DeviceListPage.test.jsx @@ -5,7 +5,7 @@ import { beforeEach, describe, expect } from "@jest/globals"; import DeviceListPage from "../DeviceListPage"; import Page from "../../general/Page"; -import DeviceListTabTable from "../DeviceListTabTable"; +import DeviceListPageContents from "../DeviceListPageContents"; Enzyme.configure({ adapter: new Adapter() }); @@ -30,8 +30,8 @@ describe(" functional component", () => { expect(page.props().breadcrumbs).toStrictEqual(expectedBreadcumbs); expect(page.props().deviceList).toBe(true); }); - it("Contains 1 component", () => { - expect(wrapper.find(DeviceListTabTable)).toHaveLength(1); + it("Contains 1 component", () => { + expect(wrapper.find(DeviceListPageContents)).toHaveLength(1); }); }); }); diff --git a/frontend/src/devicelist/__tests__/DeviceListPageContents.test.jsx b/frontend/src/devicelist/__tests__/DeviceListPageContents.test.jsx new file mode 100644 index 000000000..f9c183eff --- /dev/null +++ b/frontend/src/devicelist/__tests__/DeviceListPageContents.test.jsx @@ -0,0 +1,197 @@ +import React from "react"; +import Enzyme from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { + afterEach, + beforeEach, + describe, + expect, + jest, + it +} from "@jest/globals"; + +import DeviceListPageContents from "../DeviceListPageContents"; +import DeviceTableTitle from "../DeviceTableTitle"; +import DevicesTable from "../DevicesTable"; + +import * as DeviceApi from "../../api/DeviceApi"; +import DeviceInfo from "../../model/DeviceInfo"; + +Enzyme.configure({ adapter: new Adapter() }); + +describe(" class component", () => { + let wrapper; + const dummySenders = [new DeviceInfo("Beep")]; + const dummyReceivers = [new DeviceInfo("boop!")]; + + afterEach(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + + describe("componentDidMount() function", () => { + it("calls DeviceApi.getSenders() and getReceivers()", () => { + jest.spyOn(DeviceApi, "getSenders").mockImplementation(() => { + return dummySenders; + }); + jest.spyOn(DeviceApi, "getReceivers").mockImplementation(() => { + return dummyReceivers; + }); + wrapper = Enzyme.shallow(, { + disableLifecycleMethods: true + }); + wrapper.instance().componentDidMount(); + + expect(DeviceApi.getSenders).toHaveBeenCalledWith( + wrapper.instance().handleSendersChange + ); + expect(DeviceApi.getReceivers).toHaveBeenCalledWith( + wrapper.instance().handleReceiversChange + ); + }); + }); + describe("handleChange()", () => { + beforeEach(() => { + wrapper = Enzyme.shallow(); + }); + it("should set the state", () => { + const startingState = { + selected: 0, + senders: [], + receivers: [] + }; + + const expectedValue = 5; + + wrapper.setState(startingState); + + wrapper.instance().handleChange(expectedValue); + expect(wrapper.state().selected).toStrictEqual(expectedValue); + }); + }); + describe("handleSendersChange()", () => { + beforeEach(() => { + wrapper = Enzyme.shallow(); + }); + it("should set the state", () => { + const startingState = { + selected: 0, + senders: [], + receivers: [] + }; + + const expectedValue = [new DeviceInfo("thing!")]; + + wrapper.setState(startingState); + + wrapper.instance().handleSendersChange(expectedValue); + expect(wrapper.state().senders).toStrictEqual(expectedValue); + }); + }); + describe("handleReceiversChange()", () => { + beforeEach(() => { + wrapper = Enzyme.shallow(); + }); + it("should set the state", () => { + const startingState = { + selected: 0, + senders: [], + receivers: [] + }; + + const expectedValue = [new DeviceInfo("thing!")]; + + wrapper.setState(startingState); + + wrapper.instance().handleReceiversChange(expectedValue); + expect(wrapper.state().receivers).toStrictEqual(expectedValue); + }); + }); + describe("getDevices() function", () => { + beforeEach(() => { + wrapper = Enzyme.shallow(); + }); + it("if state.selected == 0, returns and array that combines state.senders and state.receivers ", () => { + const someState = { + selected: 0, + senders: dummySenders, + receivers: dummyReceivers + }; + wrapper.setState(someState); + const result = wrapper.instance().getDevices(); + + expect(result).toStrictEqual( + someState.senders.concat(someState.receivers) + ); + }); + it("if state.selected == 1, returns state.senders ", () => { + const someState = { + selected: 1, + senders: dummySenders, + receivers: dummyReceivers + }; + wrapper.setState(someState); + const result = wrapper.instance().getDevices(); + + expect(result).toStrictEqual(someState.senders); + }); + it("if state.selected == 2, returns state.receivers ", () => { + const someState = { + selected: 2, + senders: dummySenders, + receivers: dummyReceivers + }; + wrapper.setState(someState); + const result = wrapper.instance().getDevices(); + + expect(result).toStrictEqual(someState.receivers); + }); + }); + describe("getTitle() function", () => { + it("returns a component with expected props", () => { + wrapper = Enzyme.shallow(); + const expected = { + index: wrapper.state().selected, + handleChange: wrapper.instance().handleChange + }; + const deviceTableTitle = wrapper.instance().getTitle(); + expect(deviceTableTitle.type).toEqual(DeviceTableTitle); + + const wrap = Enzyme.shallow(deviceTableTitle); + + const { props } = wrap.instance(); + expect(props.index).toBe(expected.index); + expect(props.handleChange).toStrictEqual(expected.handleChange); + }); + }); + describe("render() function", () => { + beforeEach(() => { + wrapper = Enzyme.shallow(); + }); + describe("returns a component that", () => { + it("Contains 1 component with expected props", () => { + const someState = { + selected: 0, + senders: dummySenders, + receivers: dummyReceivers + }; + wrapper.setState(someState); + + const component = wrapper.find(DevicesTable); + expect(component).toHaveLength(1); + const expected = { + devices: dummySenders.concat(dummyReceivers), + title: ( + + ) + }; + const props = component.at(0).props(); + expect(props.devices).toStrictEqual(expected.devices); + expect(props.title).toStrictEqual(expected.title); + }); + }); + }); +}); diff --git a/frontend/src/devicelist/__tests__/DeviceListTabTable.test.jsx b/frontend/src/devicelist/__tests__/DeviceListTabTable.test.jsx deleted file mode 100644 index ba82afe6c..000000000 --- a/frontend/src/devicelist/__tests__/DeviceListTabTable.test.jsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from "react"; -import Enzyme from "enzyme"; -import Adapter from "enzyme-adapter-react-16"; -import { beforeEach, describe, expect, it } from "@jest/globals"; -import { Box } from "@material-ui/core"; -import DeviceListTabTable from "../DeviceListTabTable"; -import TabPanel from "../TabPanel"; -import DevicesTable from "../DevicesTable"; -import VerticalTabs from "../../general/VerticalTabs"; - -Enzyme.configure({ adapter: new Adapter() }); - -describe(" class component", () => { - let wrapper; - const mockGetSenders = jest.fn(); - const mockGetReceivers = jest.fn(); - const dummySource = { - getSenders: mockGetSenders, - getReceivers: mockGetReceivers - }; - - const dummyClass = { tabs: "SomeClass" }; - beforeEach(() => { - wrapper = Enzyme.shallow( - - ); - }); - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("render() function returns a component that", () => { - it("Contains one component with expected props", () => { - expect(wrapper.find(Box)).toHaveLength(1); - // TODO: validate props - // const box = wrapper.find(Box).first(); - }); - it("Contains one component with expected props", () => { - expect(wrapper.find(VerticalTabs)).toHaveLength(1); - // TODO: validate props - // const verticalTabs = wrapper.find(VerticalTabs).first(); - }); - it("Contains two components", () => { - expect(wrapper.find(TabPanel)).toHaveLength(2); - }); - it("First has expected props", () => { - const panelProps = wrapper.find(TabPanel).first().props(); - const expectedIndex = 0; - const expectedclassName = "lightGreyBorder"; - - expect(panelProps.value).toBe(wrapper.instance().state.value); - expect(panelProps.index).toBe(expectedIndex); - expect(panelProps.className).toBe(expectedclassName); - expect(panelProps.children.type.name).toBe("DevicesTable"); - }); - it("Second has expected props", () => { - const panelProps = wrapper.find(TabPanel).last().props(); - const expectedIndex = 1; - const expectedclassName = "lightGreyBorder"; - - expect(panelProps.value).toBe(wrapper.instance().state.value); - expect(panelProps.index).toBe(expectedIndex); - expect(panelProps.className).toBe(expectedclassName); - expect(panelProps.children.type.name).toBe("DevicesTable"); - }); - it("Contains two components", () => { - expect(wrapper.find(DevicesTable)).toHaveLength(2); - }); - it("First has expected props", () => { - const deviceTableProps = wrapper.find(DevicesTable).first().props(); - const expectedTitle = "List of Senders"; - - expect(deviceTableProps.devices).toBe(wrapper.instance().state.senders); - expect(deviceTableProps.title).toBe(expectedTitle); - }); - it("Second has expected props", () => { - const DeviceTableProps = wrapper.find(DevicesTable).last().props(); - const expectedTitle = "List of Receivers"; - - expect(DeviceTableProps.devices).toBe(wrapper.instance().state.receivers); - expect(DeviceTableProps.title).toBe(expectedTitle); - }); - }); - it("handleValueChange() function sets state.value", () => { - const someValue = 57; - wrapper.instance().handleValueChange(someValue); - expect(wrapper.instance().state.value).toBe(someValue); - }); - it("handleSendersChange() function sets state.senders", () => { - const someValue = [null]; - wrapper.instance().handleSendersChange(someValue); - expect(wrapper.instance().state.senders).toStrictEqual(someValue); - }); - it("handleReceiversChange() function sets state.receivers", () => { - const someValue = [null]; - wrapper.instance().handleReceiversChange(someValue); - expect(wrapper.instance().state.receivers).toStrictEqual(someValue); - }); - it("componentDidMount() function calls functions in the passed datasource", () => { - wrapper.instance().componentDidMount(); - expect(mockGetSenders).toBeCalledWith( - wrapper.instance().handleSendersChange - ); - expect(mockGetReceivers).toBeCalledWith( - wrapper.instance().handleReceiversChange - ); - }); -}); diff --git a/frontend/src/devicelist/__tests__/DeviceTableTitle.test.jsx b/frontend/src/devicelist/__tests__/DeviceTableTitle.test.jsx new file mode 100644 index 000000000..98a8ef973 --- /dev/null +++ b/frontend/src/devicelist/__tests__/DeviceTableTitle.test.jsx @@ -0,0 +1,104 @@ +import React from "react"; +import Enzyme from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { + afterEach, + beforeEach, + describe, + expect, + jest, + it +} from "@jest/globals"; +import { MenuItem, Select } from "@material-ui/core"; + +import DeviceTableTitle from "../DeviceTableTitle"; +import StyledInput from "../StyledInput"; + +Enzyme.configure({ adapter: new Adapter() }); + +describe(" class component", () => { + let wrapper; + const dummyIndex = 4; + const mockHandleChange = jest.fn(); + + beforeEach(() => { + wrapper = Enzyme.shallow( + + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + describe("render() function", () => { + describe("returns a component that", () => { + it("Contains 1