From 6da0c2b84a12c515ce3a4cbcf1e54b6338e13f93 Mon Sep 17 00:00:00 2001 From: Deepak Date: Sat, 23 Jan 2021 18:53:53 -0600 Subject: [PATCH] Adds snapshot and begins to add login component tests --- do.sh | 2 +- package-lock.json | 121 ++++++++++++++++++ package.json | 6 +- src/App.jsx | 20 ++- src/components/Action.css | 30 ----- src/components/Action.jsx | 29 ----- src/components/Action.test.jsx | 3 - src/components/ActionsContainer.jsx | 28 ---- src/components/AllPlansComponent.jsx | 40 ------ src/components/Login-snapshot.test.jsx | 15 +++ src/components/Login.jsx | 44 +++++++ src/components/Login.test.jsx | 17 +++ src/components/Plan.jsx | 54 -------- src/components/PlanList.jsx | 28 ---- src/components/Register.jsx | 53 ++++++++ src/components/UnauthenticatedApp.jsx | 30 +++++ .../Login-snapshot.test.jsx.snap | 42 ++++++ src/context/AuthContext.jsx | 52 +------- src/context/index.jsx | 5 + src/index.jsx | 3 +- src/services/auth-service.js | 50 ++++++++ src/services/config.test.jsx | 2 +- 22 files changed, 397 insertions(+), 277 deletions(-) delete mode 100644 src/components/Action.css delete mode 100644 src/components/Action.jsx delete mode 100644 src/components/Action.test.jsx delete mode 100644 src/components/ActionsContainer.jsx delete mode 100644 src/components/AllPlansComponent.jsx create mode 100644 src/components/Login-snapshot.test.jsx create mode 100644 src/components/Login.jsx create mode 100644 src/components/Login.test.jsx delete mode 100644 src/components/Plan.jsx delete mode 100644 src/components/PlanList.jsx create mode 100644 src/components/Register.jsx create mode 100644 src/components/UnauthenticatedApp.jsx create mode 100644 src/components/__snapshots__/Login-snapshot.test.jsx.snap diff --git a/do.sh b/do.sh index 757fd93..58ac7e0 100644 --- a/do.sh +++ b/do.sh @@ -11,7 +11,7 @@ build() { run() { echo "I am ${FUNCNAME[0]}ning" - npx webpack serve + API_ROOT=http://localhost:3000/api npx webpack serve } fmt() { diff --git a/package-lock.json b/package-lock.json index 4fd64b1..ffc9054 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1105,6 +1105,16 @@ "regenerator-runtime": "^0.13.4" } }, + "@babel/runtime-corejs3": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz", + "integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + }, "@babel/template": { "version": "7.12.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", @@ -1919,6 +1929,89 @@ "@sinonjs/commons": "^1.7.0" } }, + "@testing-library/dom": { + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz", + "integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^4.2.2", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.4", + "lz-string": "^1.4.4", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.3.tgz", + "integrity": "sha512-BirBUGPkTW28ULuCwIbYo0y2+0aavHczBT6N9r3LrsswEW3pg25l1wgoE7I8QBIy1upXWkwKpYdWY7NYYP0Bxw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^7.28.1" + } + }, + "@types/aria-query": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", + "integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==", + "dev": true + }, "@types/babel__core": { "version": "7.1.12", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", @@ -2552,6 +2645,16 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -3574,6 +3677,12 @@ } } }, + "core-js-pure": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.3.tgz", + "integrity": "sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -4001,6 +4110,12 @@ "esutils": "^2.0.2" } }, + "dom-accessibility-api": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", + "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", + "dev": true + }, "dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", @@ -8100,6 +8215,12 @@ "yallist": "^4.0.0" } }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", + "dev": true + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", diff --git a/package.json b/package.json index 3228bdf..76f3803 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@babel/core": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", + "@testing-library/react": "^11.2.3", "babel-loader": "^8.2.2", "css-loader": "^5.0.1", "eslint": "^7.17.0", @@ -59,5 +60,8 @@ "react": "^17.0.1", "react-dom": "^17.0.1", "react-hot-loader": "^4.13.0" - } + }, + "browserslist": [ + "since 2017-06" + ] } diff --git a/src/App.jsx b/src/App.jsx index f08418c..7e0e167 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,19 +1,15 @@ import React from "react"; import { hot } from "react-hot-loader"; import "./App.css"; -import AllPlansComponent from "./components/AllPlansComponent.jsx"; +import UnauthenticatedApp from "./components/UnauthenticatedApp"; -class App extends React.Component { - render() { - return ( -
-

Hello, World!

-
- -
-
- ); - } +function App() { + return ( +
+

Frontend

+ +
+ ); } export default hot(module)(App); diff --git a/src/components/Action.css b/src/components/Action.css deleted file mode 100644 index c483af0..0000000 --- a/src/components/Action.css +++ /dev/null @@ -1,30 +0,0 @@ -.actionID, -.actionDescription, -.actionChunks { - margin-left: 1rem; - margin-right: 1rem; -} - -.actionCompleted { - flex: 1; -} - -.actionChunks { - flex: 4; -} - -.actionID { - flex: 4; -} - -.actionDescription { - flex: 8; -} - -.actionWrapper { - align-items: flex-end; - display: flex; - margin-top: 0.5rem; - margin-bottom: 0.5rem; - padding-top: 1rem; -} diff --git a/src/components/Action.jsx b/src/components/Action.jsx deleted file mode 100644 index 65d34de..0000000 --- a/src/components/Action.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { action } from "../types"; -import "./Action.css"; - -class Action extends React.Component { - render() { - const action = this.props.action; - const completed = "completed_on" in action; - console.log([action.completed_on, completed]); - return ( -
-
{completed ? "X" : " "}
-
- ID: {action.action_id} | {action.plan_id} -
-
{action.action_description}
-
- {action.completed_chunks}/{action.estimated_chunks} -
-
- ); - } -} - -Action.propTypes = { - action: action, -}; - -export default Action; diff --git a/src/components/Action.test.jsx b/src/components/Action.test.jsx deleted file mode 100644 index da0c2c1..0000000 --- a/src/components/Action.test.jsx +++ /dev/null @@ -1,3 +0,0 @@ -test("trying it out", () => { - expect(true).toEqual(true); -}); diff --git a/src/components/ActionsContainer.jsx b/src/components/ActionsContainer.jsx deleted file mode 100644 index e116b1f..0000000 --- a/src/components/ActionsContainer.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import Action from "./Action.jsx"; -import PropTypes from "prop-types"; -import { action } from "../types"; - -class ActionsContainer extends React.Component { - render() { - return ( -
- -
- ); - } -} - -ActionsContainer.propTypes = { - actions: PropTypes.arrayOf(action), -}; - -export default ActionsContainer; diff --git a/src/components/AllPlansComponent.jsx b/src/components/AllPlansComponent.jsx deleted file mode 100644 index e2f6053..0000000 --- a/src/components/AllPlansComponent.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import PlanList from "./PlanList.jsx"; - -class AllPlansComponent extends React.Component { - constructor(props) { - super(props); - this.state = { - plans: [], - }; - } - - getPlans() { - fetch("http://localhost:3000/api/plans", { - headers: { - Accept: "application/json", - }, - }) - .then((response) => response.json()) - .then((data) => { - this.setState({ - plans: data, - }); - }) - .catch((error) => console.error(error)); - } - - componentDidMount() { - this.getPlans(); - } - - render() { - return ( -
- -
- ); - } -} - -export default AllPlansComponent; diff --git a/src/components/Login-snapshot.test.jsx b/src/components/Login-snapshot.test.jsx new file mode 100644 index 0000000..f5049f2 --- /dev/null +++ b/src/components/Login-snapshot.test.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import renderer from "react-test-renderer"; +import Login from "./Login"; + +test("Login Snapshot", () => { + + const loginFunc = jest.fn(); + + const login = renderer.create(); + + const tree = login.toJSON(); + + expect(tree).toMatchSnapshot(); + +}); diff --git a/src/components/Login.jsx b/src/components/Login.jsx new file mode 100644 index 0000000..fdf8295 --- /dev/null +++ b/src/components/Login.jsx @@ -0,0 +1,44 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; + +export default function Login({ login }) { + const [username, setUserName] = useState(); + const [password, setPassword] = useState(); + + const handleSubmit = async (e) => { + e.preventDefault(); + + login(username, password); + }; + + return ( +
+

Please Log In

+
+ + +
+ +
+
+
+ ); +} + +Login.propTypes = { + login: PropTypes.func.isRequired, +}; diff --git a/src/components/Login.test.jsx b/src/components/Login.test.jsx new file mode 100644 index 0000000..6bb20c8 --- /dev/null +++ b/src/components/Login.test.jsx @@ -0,0 +1,17 @@ +import React from "react"; +import { render } from '@testing-library/react' +import { logRoles } from "@testing-library/dom" +// import '@testing-library/jest-dom/extend-expect' +import Login from "./Login"; + +test("Login inputs", () => { + const login = render() + + const usernameInput = login.getByRole("textbox", {name: /Username/i}) + expect(usernameInput).toBeTruthy(); + + const passwordInput = login.getByLabelText("Password") + expect(passwordInput).toBeTruthy(); + logRoles(login.container) + expect(true).toBeTruthy(); +}) diff --git a/src/components/Plan.jsx b/src/components/Plan.jsx deleted file mode 100644 index 038e736..0000000 --- a/src/components/Plan.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from "react"; -import ActionsContainer from "./ActionsContainer.jsx"; -import { plan } from "../types"; - -class Plan extends React.Component { - constructor(props) { - super(props); - this.state = { - actions: [], - }; - } - - getActions() { - fetch( - "http://localhost:3000/api/actions?" + - new URLSearchParams({ - plan_id: this.props.plan.plan_id, - }), - { - headers: { - Accept: "application/json", - }, - } - ) - .then((response) => response.json()) - .then((data) => { - this.setState({ - actions: data, - }); - }) - .catch((error) => console.error(error)); - } - - componentDidMount() { - this.getActions(); - } - - render() { - return ( -
-
-

Plan for {this.props.plan.plan_date}

-
- -
- ); - } -} - -Plan.propTypes = { - plan: plan, -}; - -export default Plan; diff --git a/src/components/PlanList.jsx b/src/components/PlanList.jsx deleted file mode 100644 index b011b4c..0000000 --- a/src/components/PlanList.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import Plan from "./Plan.jsx"; -import { plan } from "../types"; -import PropTypes from "prop-types"; - -class PlanList extends React.Component { - render() { - return ( -
-
    - {this.props.plans.map((plan) => { - return ( -
  • - -
  • - ); - })} -
-
- ); - } -} - -PlanList.propTypes = { - plans: PropTypes.arrayOf(plan), -}; - -export default PlanList; diff --git a/src/components/Register.jsx b/src/components/Register.jsx new file mode 100644 index 0000000..69f2150 --- /dev/null +++ b/src/components/Register.jsx @@ -0,0 +1,53 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; + +export default function Register({ register }) { + const [username, setUserName] = useState(); + const [displayName, setDisplayName] = useState(); + const [password, setPassword] = useState(); + + const handleSubmit = async (e) => { + e.preventDefault(); + + register(username, displayName, password); + }; + + return ( +
+

Enter your data to register.

+
+ + + +
+ +
+
+
+ ); +} + +Register.propTypes = { + register: PropTypes.func.isRequired, +}; diff --git a/src/components/UnauthenticatedApp.jsx b/src/components/UnauthenticatedApp.jsx new file mode 100644 index 0000000..cd1096a --- /dev/null +++ b/src/components/UnauthenticatedApp.jsx @@ -0,0 +1,30 @@ +import React, { useState } from "react"; +import { useAuth } from "../context/AuthContext"; +import Login from "./Login"; +import Register from "./Register"; + +function UnauthenticatedApp() { + const { login, register } = useAuth(); + + const [showRegister, setShowRegister] = useState(false); + + function toggle() { + setShowRegister(!showRegister); + } + + const form = showRegister ? ( + + ) : ( + + ); + return ( +
+ + {form} +
+ ); +} + +export default UnauthenticatedApp; diff --git a/src/components/__snapshots__/Login-snapshot.test.jsx.snap b/src/components/__snapshots__/Login-snapshot.test.jsx.snap new file mode 100644 index 0000000..bb651de --- /dev/null +++ b/src/components/__snapshots__/Login-snapshot.test.jsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Login Snapshot 1`] = ` +
+

+ Please Log In +

+
+ + +
+ +
+
+
+`; diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 6a7cf49..66c9fa9 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -1,55 +1,11 @@ import React from "react"; -import { useAsync } from "react-async"; -import { bootstrapAppData } from "../utils/bootstrap"; -import * as authClient from "../utils/auth-client"; -import { FullPageSpinner } from "../components/lib"; +import { register, login } from "../services/auth-service"; const AuthContext = React.createContext(); -function AuthProvider(props) { - const [firstAttemptFinished, setFirstAttemptFinished] = React.useState(false); - const { - data = { user: null, listItems: [] }, - error, - isRejected, - isPending, - isSettled, - reload, - } = useAsync({ - promiseFn: bootstrapAppData, - }); - - React.useLayoutEffect(() => { - if (isSettled) { - setFirstAttemptFinished(true); - } - }, [isSettled]); - - if (!firstAttemptFinished) { - if (isPending) { - return ; - } - if (isRejected) { - return ( -
-

Uh oh... There's a problem. Try refreshing the app.

-
{error.message}
-
- ); - } - } - - const login = (form) => authClient.login(form).then(reload); - const register = (form) => authClient.register(form).then(reload); - const logout = () => authClient.logout().then(reload); - - return ( - - ); -} +const AuthProvider = (props) => { + return ; +}; function useAuth() { const context = React.useContext(AuthContext); diff --git a/src/context/index.jsx b/src/context/index.jsx index 7c6dbb5..a0a8b5d 100644 --- a/src/context/index.jsx +++ b/src/context/index.jsx @@ -1,8 +1,13 @@ import React from "react"; import { AuthProvider } from "./AuthContext.jsx"; +import PropTypes from "prop-types"; function AppProviders({ children }) { return {children}; } export default AppProviders; + +AppProviders.propTypes = { + children: PropTypes.node, +}; diff --git a/src/index.jsx b/src/index.jsx index 4dba1e9..bd1cbf6 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,9 +1,8 @@ import React from "react"; import ReactDOM from "react-dom"; import App from "./App.jsx"; -ReactDOM.render(, document.getElementById("root")); - import AppProviders from "./context"; + ReactDOM.render( diff --git a/src/services/auth-service.js b/src/services/auth-service.js index e69de29..c303186 100644 --- a/src/services/auth-service.js +++ b/src/services/auth-service.js @@ -0,0 +1,50 @@ +import { API_ROOT } from "./config"; + +export const register = (username, displayName, password) => { + const url = API_ROOT + "auth/register"; + + const body = { + username: username, + display_name: displayName, + password: password, + }; + return fetch(url, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); +}; + +export const login = (username, password) => { + const url = API_ROOT + "auth/tokens"; + + const body = { + username: username, + password: password, + }; + return fetch(url, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }) + .then((response) => { + if (!response.ok) { + throw Error(response.statusText); + } + return response.json(); + }) + .then((data) => { + console.log(data); + if (data.token) { + console.log(data.token); + return data.token; + } + throw Error("where's my token"); + }); +}; diff --git a/src/services/config.test.jsx b/src/services/config.test.jsx index ca52518..b339eed 100644 --- a/src/services/config.test.jsx +++ b/src/services/config.test.jsx @@ -1,4 +1,4 @@ -import { API_ROOT } from "./config" +import { API_ROOT } from "./config"; test("testing config api root", () => { expect(API_ROOT).toEqual("http://localhost:8080/");