diff --git a/pwa/package-lock.json b/pwa/package-lock.json index caea114..9c88623 100644 --- a/pwa/package-lock.json +++ b/pwa/package-lock.json @@ -1678,6 +1678,11 @@ "@types/node": "*" } }, + "@types/history": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz", + "integrity": "sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw==" + }, "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", @@ -1755,6 +1760,25 @@ "@types/react": "*" } }, + "@types/react-router": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.5.tgz", + "integrity": "sha512-RZPdCtZympi6X7EkGyaU7ISiAujDYTWgqMF9owE3P6efITw27IWQykcti0BvA5h4Mu1LLl5rxrpO3r8kHyUZ/Q==", + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.4.tgz", + "integrity": "sha512-LO0z5qqSfWdYtCNsRm8/OMnnkv52hwADJKrAfpKIyfHclORllcgAMGypEA7ajHm38+jOonKLx8nPygNAAZGxtg==", + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, "@types/react-test-renderer": { "version": "16.9.2", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz", @@ -6636,6 +6660,11 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "gzip-size": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", @@ -6771,6 +6800,19 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -6781,6 +6823,14 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "hosted-git-info": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", @@ -9586,6 +9636,16 @@ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=" }, + "mini-create-react-context": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", + "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "requires": { + "@babel/runtime": "^7.4.0", + "gud": "^1.0.0", + "tiny-warning": "^1.0.2" + } + }, "mini-css-extract-plugin": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", @@ -12269,6 +12329,52 @@ "react-tween-state": "^0.1.5" } }, + "react-router": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", + "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.3.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "react-router-dom": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", + "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.1.2", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-scripts": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.3.1.tgz", @@ -12980,6 +13086,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -14891,6 +15002,16 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tinycolor2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", @@ -15364,6 +15485,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/pwa/package.json b/pwa/package.json index 2e874b8..9abda9f 100644 --- a/pwa/package.json +++ b/pwa/package.json @@ -11,10 +11,12 @@ "@types/node": "^12.12.26", "@types/react": "^16.9.19", "@types/react-dom": "^16.9.5", + "@types/react-router-dom": "^5.1.4", "antd-mobile": "^2.3.1", "node-sass": "^4.13.1", "react": "^16.12.0", "react-dom": "^16.12.0", + "react-router-dom": "^5.1.2", "react-scripts": "3.3.1", "typescript": "^3.7.5" }, diff --git a/pwa/src/App.tsx b/pwa/src/App.tsx index 4006c0f..016b820 100644 --- a/pwa/src/App.tsx +++ b/pwa/src/App.tsx @@ -1,14 +1,38 @@ import React from "react"; +import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; + +import Login from "./components/Login"; +import LandingPage from "./components/LandingPage"; +import PatientInfoForm from "./components/PatientInfoForm"; +import Wrapper from "./Wrapper"; + import "./scss/App.scss"; import "./antd-mobile.css"; -import Login from "./components/Login"; - const App = () => { return ( - <> - - + + + + + + + + + + + + + + + + + + ); }; diff --git a/pwa/src/Wrapper.test.tsx b/pwa/src/Wrapper.test.tsx new file mode 100644 index 0000000..00848e8 --- /dev/null +++ b/pwa/src/Wrapper.test.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { render, queryByAttribute, fireEvent } from "@testing-library/react"; +import { useHistory } from "react-router-dom"; +import Wrapper from "./Wrapper"; + +const mockHistoryPush = jest.fn(); +jest.mock("react-router-dom", () => ({ + useHistory: () => ({ + push: mockHistoryPush, + }), +})); + +test("renders it's inner contents", () => { + const dom = render( + +
Hello!
+
+ ); + + const textContents = dom.getByText("Hello!"); + + expect(textContents).toBeInTheDocument(); +}); + +test("renders navBar when given navTitle", () => { + const dom = render( + +
Hello!
+
+ ); + + const getById = queryByAttribute.bind(null, "id"); + const renderedNavBar = getById(dom.container, "nav-bar"); + const navTitle = dom.getByText("CHI"); + + expect(renderedNavBar).toBeTruthy(); + expect(navTitle).toBeInTheDocument(); +}); + +test("renders no navBar when not given props", () => { + const dom = render( + +
Hello!
+
+ ); + const getById = queryByAttribute.bind(null, "id"); + const renderedNavBar = getById(dom.container, "nav-bar"); + expect(renderedNavBar).toBeFalsy(); +}); + +test("renders leftArrowText", () => { + const dom = render( + +
Hello!
+
+ ); + + const leftArrowText = dom.getByText("back"); + + expect(leftArrowText).toBeInTheDocument(); +}); + +test("redirects on left arrow click ", () => { + const dom = render( + +
Hello!
+
+ ); + + const leftArrowText = dom.getByText("back"); + fireEvent.click(leftArrowText); + + expect(useHistory().push).toBeCalledWith("/"); +}); diff --git a/pwa/src/Wrapper.tsx b/pwa/src/Wrapper.tsx new file mode 100644 index 0000000..4a76bc0 --- /dev/null +++ b/pwa/src/Wrapper.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { NavBar, Icon } from "antd-mobile"; +import { useHistory } from "react-router-dom"; + +import "./scss/Wrapper.scss"; + +type props = { + navTitle?: string; + leftArrowText?: string; + leftArrowRoute?: string; + children: React.ReactNode; +}; + +const Wrapper = (props: props) => { + const { navTitle, leftArrowText, leftArrowRoute } = props; + const hist = useHistory(); + const needNavBar = navTitle || leftArrowText || leftArrowRoute; + const leftContentArr = leftArrowText + ? [, `${leftArrowText}`] + : []; + const leftRoute = leftArrowRoute ? `${leftArrowRoute}` : "/"; + + return ( +
+
+ {needNavBar && ( + hist.push(leftRoute)} + leftContent={leftContentArr} + > + {`${navTitle}`} + + )} + {props.children} +
+
+ ); +}; + +export default Wrapper; diff --git a/pwa/src/components/LandingPage.tsx b/pwa/src/components/LandingPage.tsx index 3a787c4..b8a0df4 100644 --- a/pwa/src/components/LandingPage.tsx +++ b/pwa/src/components/LandingPage.tsx @@ -1,11 +1,12 @@ import React, { ReactElement } from "react"; import { WhiteSpace, Button } from "antd-mobile"; -import Icon, { UserAddOutlined } from "@ant-design/icons"; +import { UserAddOutlined } from "@ant-design/icons"; import "../scss/LandingPage.scss"; export default function LandingPage(): ReactElement { /* istanbul ignore next */ + // TODO: these onClick functions could be mocked in a unit test const patientLookup = () => { console.log("Clicked Patient Lookup"); }; diff --git a/pwa/src/components/Login.tsx b/pwa/src/components/Login.tsx index 5766b06..d181260 100644 --- a/pwa/src/components/Login.tsx +++ b/pwa/src/components/Login.tsx @@ -1,31 +1,29 @@ -import React, { ReactElement } from 'react' -import useApi from '../hooks/useApi' -import {InputItem, Button} from 'antd-mobile' -import '../scss/Login.scss' +import React, { ReactElement } from "react"; +import useApi from "../hooks/useApi"; +import { InputItem, Button } from "antd-mobile"; +import "../scss/Login.scss"; export default function Login(): ReactElement { - const {response, error, isLoading} = useApi("") + const { response, isLoading } = useApi(""); return ( <>
-
-
- CHI Logo -
-
- - -
- +
+
+ CHI Logo
+
+ + +
+ +

Loading API Fetch: {isLoading.toString()}

- {response && -

API Fetch Result: {JSON.stringify(response)}

- } + {response &&

API Fetch Result: {JSON.stringify(response)}

} - ) + ); } diff --git a/pwa/src/components/PatientInfoForm.test.tsx b/pwa/src/components/PatientInfoForm.test.tsx new file mode 100644 index 0000000..b1009fd --- /dev/null +++ b/pwa/src/components/PatientInfoForm.test.tsx @@ -0,0 +1,105 @@ +import React from "react"; +import { render, fireEvent, prettyDOM } from "@testing-library/react"; +import PatientInfoForm from "./PatientInfoForm"; + +test("female-only div is only displayed if the sex is selected as female", async () => { + const femaleOnlyFormFields = ["Pregnant?", "LMP", "Wants Planning?"]; + + const dom = render(); + + // ensure the female-only div is not displayed + femaleOnlyFormFields.forEach((fieldText) => { + expect(dom.queryByText(fieldText)).toBeNull(); + }); + + // click the radio button for 'female' + const femaleButton = dom.getByTestId("female-button"); + fireEvent.click(femaleButton); + + femaleOnlyFormFields.forEach((fieldText) => { + expect(dom.queryByText(fieldText)).toBeTruthy(); + }); +}); + +test("sets visit input", () => { + const dom = render(); + const visitYesInput = dom.queryByTestId("visit-Yes"); + const checkBox = visitYesInput.getElementsByTagName("span")[0]; + + expect(checkBox).not.toHaveClass("am-radio-checked"); + fireEvent.click(visitYesInput); + expect(checkBox).toHaveClass("am-radio-checked"); +}); + +test("sets pregnant input", () => { + const dom = render(); + const femaleButton = dom.getByTestId("female-button"); + fireEvent.click(femaleButton); + + const pregnantYesInput = dom.queryByTestId("pregnant-Yes"); + const checkBox = pregnantYesInput.getElementsByTagName("span")[0]; + + expect(checkBox).not.toHaveClass("am-radio-checked"); + fireEvent.click(pregnantYesInput); + expect(checkBox).toHaveClass("am-radio-checked"); +}); + +test("sets planning input", () => { + const dom = render(); + const femaleButton = dom.getByTestId("female-button"); + fireEvent.click(femaleButton); + + const planningYesInput = dom.queryByTestId("planning-Yes"); + const checkBox = planningYesInput.getElementsByTagName("span")[0]; + + expect(checkBox).not.toHaveClass("am-radio-checked"); + fireEvent.click(planningYesInput); + expect(checkBox).toHaveClass("am-radio-checked"); +}); + +test("sets albendazole input", () => { + const dom = render(); + const femaleButton = dom.getByTestId("female-button"); + + const albendazoleYesInput = dom.queryByTestId("albendazole-Yes"); + const checkBox = albendazoleYesInput.getElementsByTagName("span")[0]; + + expect(checkBox).not.toHaveClass("am-radio-checked"); + fireEvent.click(albendazoleYesInput); + expect(checkBox).toHaveClass("am-radio-checked"); +}); + +test("renders the Patient Info Form fields", () => { + const formFields = [ + "First Name", + "First Name", + "Last Name", + "Last Name", + "Sex", + "Birth", + "Age", + "Phone", + "Town", + "Visit", + "Nickname", + "BP", + "Temp", + "Pulse", + "Weight", + "Height", + "Z-Score", + "Albendazole", + "Alergies", + "Medicines", + "Chief Complaint", + "History", + "Exam", + ]; + + const { getByText } = render(); + + formFields.forEach((fieldText) => { + let re = new RegExp(fieldText, "gi"); + expect(getByText(re)).toBeInTheDocument(); + }); +}); diff --git a/pwa/src/components/PatientInfoForm.tsx b/pwa/src/components/PatientInfoForm.tsx new file mode 100644 index 0000000..980d8e8 --- /dev/null +++ b/pwa/src/components/PatientInfoForm.tsx @@ -0,0 +1,264 @@ +import React, { ReactElement, useState } from "react"; +import { + DatePicker, + InputItem, + List, + TextareaItem, + Button, + Radio, + WhiteSpace, +} from "antd-mobile"; +import enUs from "antd-mobile/lib/date-picker/locale/en_US"; +import "../scss/PatientInfoForm.scss"; + +const RadioItem = Radio.RadioItem; + +export default function Form(): ReactElement { + // State Hooks for Radio Buttons + // useState returns a value, and a function to set that value + const [sex, setSex] = useState(null as Number); // Default: null + const [albendazole, setAlbendazole] = useState(0); // Default: No + const [visit, setVisit] = useState(0); // Default: No + const [pregnant, setPregnant] = useState(0); // Default: No + const [planning, setPlanning] = useState(0); // Default: No + + // Data for Radio Buttons + const sexValues = [ + { value: 0, label: "Male" }, + { value: 1, label: "Female" }, + ]; + + const radioYesNo = [ + { value: 0, label: "No" }, + { value: 1, label: "Yes" }, + ]; + + /* istanbul ignore next */ + const onSubmit = () => { + // This shows how to access the values of the form + const firstNameField: HTMLInputElement = document.getElementById( + "firstname" + ) as HTMLInputElement; + const lastNameField: HTMLInputElement = document.getElementById( + "lastname" + ) as HTMLInputElement; + const nicknameField: HTMLInputElement = document.getElementById( + "nickname" + ) as HTMLInputElement; + const birthdateField: HTMLInputElement = document.getElementById( + "birthdate" + ) as HTMLInputElement; + const complaintField1: HTMLInputElement = document.getElementById( + "complaint1" + ) as HTMLInputElement; + const complaintField2: HTMLInputElement = document.getElementById( + "complaint2" + ) as HTMLInputElement; + const complaintField3: HTMLInputElement = document.getElementById( + "complaint3" + ) as HTMLInputElement; + + console.log("First name:", firstNameField.value); + console.log("Last name:", lastNameField.value); + console.log("Nickname:", nicknameField.value); + console.log("Birth Date:", birthdateField.value); + console.log( + "Complaints: ", + complaintField1.value + + " " + + complaintField2.value + + " " + + complaintField3.value + ); + console.log("Wants Planning?: " + planning); + + }; + + return ( +
+ + + + + + + "Sex"}> + {sexValues.map((i) => ( + setSex(i.value)} + > + {i.label} + + ))} + + + + + + Birth Date + + + + Age{" "} + + + + Phone + + + + + "Visit?"}> + {radioYesNo.map((i) => ( + setVisit(i.value)} + > + {i.label} + + ))} + + + {/* Only display the 'female-only' div if the patient is female */} + {sex ? ( +
+ + "Pregnant?"}> + {radioYesNo.map((i) => ( + setPregnant(i.value)} + > + {i.label} + + ))} + + + + G + + + + P + + + + A + + + + + "Wants Planning?"}> + {radioYesNo.map((i) => ( + setPlanning(i.value)} + > + {i.label} + + ))} + +
+ ) : null} + + + + + Weight (kg) + + + + Height (cm) + + + + BP + + + + Temp (F) + + + + Pulse + + + + Z-Score + + + "Albendazole?"}> + {radioYesNo.map((i) => ( + setAlbendazole(i.value)} + > + {i.label} + + ))} + + + "Alergies"}> + + + + "Medicines"}> + + + + "Chief Complaint"}> + + + + + + "History"}> + + + + "Exam"}> + + + + + + +
+ ); +} diff --git a/pwa/src/hooks/useApi.test.tsx b/pwa/src/hooks/useApi.test.tsx index 42286bf..aafd6d8 100644 --- a/pwa/src/hooks/useApi.test.tsx +++ b/pwa/src/hooks/useApi.test.tsx @@ -1,16 +1,28 @@ -import { renderHook } from '@testing-library/react-hooks' -import useApi from './useApi' +import { renderHook } from "@testing-library/react-hooks"; +import useApi from "./useApi"; -test('should return loading and then a response', async () => { - const { result, waitForNextUpdate } = renderHook(() => useApi("")) - - expect(result.current.response).toBeNull() - expect(result.current.error).toBeNull() - expect(result.current.isLoading).toBeTruthy() +test("should return loading and then a response", async () => { + const { result, waitForNextUpdate } = renderHook(() => useApi("")); - await waitForNextUpdate() + expect(result.current.response).toBeNull(); + expect(result.current.error).toBeNull(); + expect(result.current.isLoading).toBeTruthy(); - expect(result.current.response).toBeTruthy() - expect(result.current.error).toBeNull() - expect(result.current.isLoading).toBeFalsy() -}) \ No newline at end of file + await waitForNextUpdate(); + + expect(result.current.response).toBeTruthy(); + expect(result.current.error).toBeNull(); + expect(result.current.isLoading).toBeFalsy(); +}); + +test("should return an error on a non existent route", async () => { + const { result, waitForNextUpdate } = renderHook(() => useApi("/404")); + + expect(result.current.response).toBeNull(); + expect(result.current.error).toBeNull(); + + await waitForNextUpdate(); + + expect(result.current.response).toBeFalsy(); + expect(result.current.error).toBeTruthy(); +}); diff --git a/pwa/src/hooks/useApi.tsx b/pwa/src/hooks/useApi.tsx index 50eb0cd..a292d8b 100644 --- a/pwa/src/hooks/useApi.tsx +++ b/pwa/src/hooks/useApi.tsx @@ -1,19 +1,18 @@ -import React from 'react' +import React from "react"; const useApi = (route: string) => { const [response, setResponse] = React.useState(null); const [error, setError] = React.useState(null); const [isLoading, setIsLoading] = React.useState(false); - + /* istanbul ignore next */ const getUrl = () => { - if (process.env.environment === "docker"){ - return "http://api:4000/" + if (process.env.environment === "docker") { + return "http://api:4000/"; + } else { + return "http://localhost:4000/"; } - else { - return "http://localhost:4000/" - } - } + }; React.useEffect(() => { const fetchData = async () => { @@ -22,14 +21,15 @@ const useApi = (route: string) => { const res = await fetch(getUrl() + route); const json = await res.json(); setResponse(json); - setIsLoading(false) + setIsLoading(false); } catch (error) { setError(error); } }; fetchData(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { response, error, isLoading }; }; -export default useApi \ No newline at end of file +export default useApi; diff --git a/pwa/src/scss/LandingPage.scss b/pwa/src/scss/LandingPage.scss index 905257e..73ed522 100644 --- a/pwa/src/scss/LandingPage.scss +++ b/pwa/src/scss/LandingPage.scss @@ -1,6 +1,4 @@ .button-flex { - animation-name: fade-in; - animation-duration: 2s; display: flex; justify-content: center; margin: 2em 0; diff --git a/pwa/src/scss/Login.scss b/pwa/src/scss/Login.scss index ea78e88..f2dd399 100644 --- a/pwa/src/scss/Login.scss +++ b/pwa/src/scss/Login.scss @@ -21,7 +21,7 @@ #imgWrap { width: 28%; margin: auto; - padding: .2rem; + padding: 0.2rem; background-color: #eee; border-radius: 5px; img { @@ -37,13 +37,13 @@ animation-name: fade-in; animation-duration: 2s; } - #buttonFlex{ + #buttonFlex { animation-name: fade-in; animation-duration: 2s; display: flex; justify-content: center; .am-button { - background-color: #9BD7FF; + background-color: #9bd7ff; width: 40%; &.am-button-active { background-color: #ccebff; @@ -51,21 +51,29 @@ } } } -} + $animationDelay: 1; + @for $i from 1 through 15 { + .loginGrid :nth-child(#{$i}) { + animation-delay: #{0.3+ ($i)/30}s; + } + } -$animationDelay: 1; -@for $i from 1 through 15 { - .loginGrid :nth-child(#{$i}) { - animation-delay: #{0.3+($i)/30}s; + @keyframes fade-in { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + + @keyframes fade-in-from-up { + 0% { + opacity: 0; + transform: translateY(-100px); + } + 100% { + opacity: 1; + } } } - -@keyframes fade-in { - 0% { opacity: 0; } - 100% { opacity: 1; } -} - -@keyframes fade-in-from-up { - 0% { opacity: 0; transform: translateY(-100px)} - 100% { opacity: 1; } -} \ No newline at end of file diff --git a/pwa/src/scss/PatientInfoForm.scss b/pwa/src/scss/PatientInfoForm.scss new file mode 100644 index 0000000..6451bb9 --- /dev/null +++ b/pwa/src/scss/PatientInfoForm.scss @@ -0,0 +1,8 @@ +#patient-info-wrapper { + display: flex; + flex-direction: column; + + height: 100%; + width: 100%; + } + \ No newline at end of file diff --git a/pwa/src/scss/Wrapper.scss b/pwa/src/scss/Wrapper.scss new file mode 100644 index 0000000..ed3f753 --- /dev/null +++ b/pwa/src/scss/Wrapper.scss @@ -0,0 +1,11 @@ +#primary-wrapper { + width: 100%; + height: 100%; + display: flex; + justify-content: space-around; + #wrapper-contents { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + max-width: 50em; + max-height: 94.6vh; + } +}