From 811bf03a641c68ad975b67f717a9fe310dffbde0 Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Wed, 8 Jul 2020 14:21:13 -0400 Subject: [PATCH 01/19] Add Auth component to keep track of auth status --- frontend/src/components/Auth/index.js | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 frontend/src/components/Auth/index.js diff --git a/frontend/src/components/Auth/index.js b/frontend/src/components/Auth/index.js new file mode 100644 index 00000000..4c411367 --- /dev/null +++ b/frontend/src/components/Auth/index.js @@ -0,0 +1,34 @@ +import React from 'react'; +import app from '../Firebase'; + +const AuthContext = React.createContext(); + +/** + * AuthProvider component that keeps track of the authentication status of the + * current user. Allows global use of this status using React Context and should + * be wrapped around the contents of the App component. + */ +class AuthProvider extends React.Component { + constructor(props) { + super(props); + this.state = {currentUser: null}; + } + + componentDidMount() { + // Set up listener that runs whenever the authentication status of the user + // changes. + app.auth().onAuthStateChanged((user) => { + this.state.currentUser = user; + }) + } + + render() { + return ( + + {this.props.children} + + ); + } +} + +export { AuthContext, AuthProvider }; From 2e849c2b6574e32b9d82f8a55b5ba74c4eb37952 Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Thu, 9 Jul 2020 16:04:56 -0400 Subject: [PATCH 02/19] Make AuthProvider a function component --- frontend/src/components/Auth/index.js | 29 +++++++++------------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/Auth/index.js b/frontend/src/components/Auth/index.js index 4c411367..c73d6311 100644 --- a/frontend/src/components/Auth/index.js +++ b/frontend/src/components/Auth/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import app from '../Firebase'; const AuthContext = React.createContext(); @@ -8,27 +8,18 @@ const AuthContext = React.createContext(); * current user. Allows global use of this status using React Context and should * be wrapped around the contents of the App component. */ -class AuthProvider extends React.Component { - constructor(props) { - super(props); - this.state = {currentUser: null}; - } +const AuthProvider = ({ children }) => { + const [currentUser, setCurrentUser] = useState(null); - componentDidMount() { - // Set up listener that runs whenever the authentication status of the user - // changes. - app.auth().onAuthStateChanged((user) => { - this.state.currentUser = user; - }) - } + useEffect(() => { + app.auth().onAuthStateChanged(setCurrentUser); + }, []); - render() { - return ( - - {this.props.children} + return ( + + {children} - ); - } + ); } export { AuthContext, AuthProvider }; From 97bf09503fffc4fd648651c55e51543a201fd050 Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Thu, 9 Jul 2020 16:05:53 -0400 Subject: [PATCH 03/19] Add PrivateRoute component --- frontend/src/components/PrivateRoute/index.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 frontend/src/components/PrivateRoute/index.js diff --git a/frontend/src/components/PrivateRoute/index.js b/frontend/src/components/PrivateRoute/index.js new file mode 100644 index 00000000..3497062c --- /dev/null +++ b/frontend/src/components/PrivateRoute/index.js @@ -0,0 +1,27 @@ +import React, { useContext } from 'react'; +import { Route, Redirect } from 'react-router-dom'; +import { AuthContext } from '../Auth'; + +import { SIGN_IN } from '../../constants/routes.js'; + +/** + * PrivateRoute component that takes the authentication status of the current + * user from AuthContext. If they are authenticated, they will be allows to view + * the contents of the Route component. If they are not authenticated, they will + * be redirected to the SIGN_IN page. + */ +const PrivateRoute = ({ component: RouteComponent, ...rest }) => { + const {currentUser} = useContext(AuthContext); + + return ( + + currentUser ? + : + } + /> + ); +} + +export default PrivateRoute; From 052f25289c6f570cfcf634713a0cbde71f5d1adf Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Thu, 9 Jul 2020 16:07:33 -0400 Subject: [PATCH 04/19] Make some pages private routes --- frontend/src/components/App/index.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/App/index.js b/frontend/src/components/App/index.js index 315d0dce..33ac5f68 100644 --- a/frontend/src/components/App/index.js +++ b/frontend/src/components/App/index.js @@ -1,5 +1,7 @@ import React from 'react'; import {BrowserRouter as Router, Route} from 'react-router-dom'; +import { AuthProvider } from '../Auth'; +import PrivateRoute from '../PrivateRoute'; import LandingPage from '../Landing'; import SignInPage from '../SignIn' @@ -14,14 +16,16 @@ import * as ROUTES from '../../constants/routes'; class App extends React.Component { render() { return ( - -
- - - - -
-
+ + +
+ + + + +
+
+
); } }; From 3061b84486f8b8d8a188508f130b5b3ade519ede Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Thu, 9 Jul 2020 18:02:10 -0400 Subject: [PATCH 05/19] Add Loading screen to wait for authentication --- frontend/src/components/Auth/index.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Auth/index.js b/frontend/src/components/Auth/index.js index c73d6311..ab91208f 100644 --- a/frontend/src/components/Auth/index.js +++ b/frontend/src/components/Auth/index.js @@ -8,17 +8,25 @@ const AuthContext = React.createContext(); * current user. Allows global use of this status using React Context and should * be wrapped around the contents of the App component. */ -const AuthProvider = ({ children }) => { +const AuthProvider = (props) => { const [currentUser, setCurrentUser] = useState(null); + const [pending, setPending] = useState(true); useEffect(() => { - app.auth().onAuthStateChanged(setCurrentUser); + app.auth().onAuthStateChanged((user) => { + setCurrentUser(user); + setPending(false); + }); }, []); + if (pending) { + return (

Loading...

); + } + return ( - {children} - + {props.children} +
); } From a9394967e69687b0a0acc6bb15c2d616b1b3939b Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Thu, 9 Jul 2020 23:50:37 -0400 Subject: [PATCH 06/19] Fix typo --- frontend/src/components/PrivateRoute/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/PrivateRoute/index.js b/frontend/src/components/PrivateRoute/index.js index 3497062c..0295d7b6 100644 --- a/frontend/src/components/PrivateRoute/index.js +++ b/frontend/src/components/PrivateRoute/index.js @@ -6,9 +6,9 @@ import { SIGN_IN } from '../../constants/routes.js'; /** * PrivateRoute component that takes the authentication status of the current - * user from AuthContext. If they are authenticated, they will be allows to view - * the contents of the Route component. If they are not authenticated, they will - * be redirected to the SIGN_IN page. + * user from AuthContext. If they are authenticated, they will be allowed to + * view the contents of the Route component. If they are not authenticated, they + * will be redirected to the SIGN_IN page. */ const PrivateRoute = ({ component: RouteComponent, ...rest }) => { const {currentUser} = useContext(AuthContext); From bdb33bd24b372ea4980b291fd7ea87a79de9d0ef Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 00:25:19 -0400 Subject: [PATCH 07/19] Restructure files --- frontend/src/components/App/index.js | 3 +- frontend/src/components/Auth/AuthContext.js | 33 +++++++++++++++++ .../index.js => Auth/PrivateRoute.js} | 0 frontend/src/components/Auth/index.js | 35 ++----------------- 4 files changed, 37 insertions(+), 34 deletions(-) create mode 100644 frontend/src/components/Auth/AuthContext.js rename frontend/src/components/{PrivateRoute/index.js => Auth/PrivateRoute.js} (100%) diff --git a/frontend/src/components/App/index.js b/frontend/src/components/App/index.js index 33ac5f68..544d9c35 100644 --- a/frontend/src/components/App/index.js +++ b/frontend/src/components/App/index.js @@ -1,7 +1,6 @@ import React from 'react'; import {BrowserRouter as Router, Route} from 'react-router-dom'; -import { AuthProvider } from '../Auth'; -import PrivateRoute from '../PrivateRoute'; +import { AuthProvider, PrivateRoute } from '../Auth'; import LandingPage from '../Landing'; import SignInPage from '../SignIn' diff --git a/frontend/src/components/Auth/AuthContext.js b/frontend/src/components/Auth/AuthContext.js new file mode 100644 index 00000000..ab91208f --- /dev/null +++ b/frontend/src/components/Auth/AuthContext.js @@ -0,0 +1,33 @@ +import React, { useState, useEffect } from 'react'; +import app from '../Firebase'; + +const AuthContext = React.createContext(); + +/** + * AuthProvider component that keeps track of the authentication status of the + * current user. Allows global use of this status using React Context and should + * be wrapped around the contents of the App component. + */ +const AuthProvider = (props) => { + const [currentUser, setCurrentUser] = useState(null); + const [pending, setPending] = useState(true); + + useEffect(() => { + app.auth().onAuthStateChanged((user) => { + setCurrentUser(user); + setPending(false); + }); + }, []); + + if (pending) { + return (

Loading...

); + } + + return ( + + {props.children} + + ); +} + +export { AuthContext, AuthProvider }; diff --git a/frontend/src/components/PrivateRoute/index.js b/frontend/src/components/Auth/PrivateRoute.js similarity index 100% rename from frontend/src/components/PrivateRoute/index.js rename to frontend/src/components/Auth/PrivateRoute.js diff --git a/frontend/src/components/Auth/index.js b/frontend/src/components/Auth/index.js index ab91208f..6e7e2e4d 100644 --- a/frontend/src/components/Auth/index.js +++ b/frontend/src/components/Auth/index.js @@ -1,33 +1,4 @@ -import React, { useState, useEffect } from 'react'; -import app from '../Firebase'; +import { AuthContext, AuthProvider } from './AuthContext.js'; +import PrivateRoute from './PrivateRoute.js'; -const AuthContext = React.createContext(); - -/** - * AuthProvider component that keeps track of the authentication status of the - * current user. Allows global use of this status using React Context and should - * be wrapped around the contents of the App component. - */ -const AuthProvider = (props) => { - const [currentUser, setCurrentUser] = useState(null); - const [pending, setPending] = useState(true); - - useEffect(() => { - app.auth().onAuthStateChanged((user) => { - setCurrentUser(user); - setPending(false); - }); - }, []); - - if (pending) { - return (

Loading...

); - } - - return ( - - {props.children} - - ); -} - -export { AuthContext, AuthProvider }; +export { AuthContext, AuthProvider, PrivateRoute }; From 98dfecb28ab16435bf73b17df87953a5cb99a6db Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 03:47:33 -0400 Subject: [PATCH 08/19] Add tests --- frontend/src/components/Auth/AuthContext.js | 7 +- .../src/components/Auth/AuthContext.test.js | 82 +++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/Auth/AuthContext.test.js diff --git a/frontend/src/components/Auth/AuthContext.js b/frontend/src/components/Auth/AuthContext.js index ab91208f..7c6b6b03 100644 --- a/frontend/src/components/Auth/AuthContext.js +++ b/frontend/src/components/Auth/AuthContext.js @@ -10,16 +10,17 @@ const AuthContext = React.createContext(); */ const AuthProvider = (props) => { const [currentUser, setCurrentUser] = useState(null); - const [pending, setPending] = useState(true); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { + // Set up listener that changes user status whenever it is updated. app.auth().onAuthStateChanged((user) => { setCurrentUser(user); - setPending(false); + setIsLoading(false); }); }, []); - if (pending) { + if (isLoading) { return (

Loading...

); } diff --git a/frontend/src/components/Auth/AuthContext.test.js b/frontend/src/components/Auth/AuthContext.test.js new file mode 100644 index 00000000..a54cb00b --- /dev/null +++ b/frontend/src/components/Auth/AuthContext.test.js @@ -0,0 +1,82 @@ +import React, { useContext } from 'react'; +import { render, act, screen, cleanup } from '@testing-library/react'; +import { AuthContext, AuthProvider } from './index'; + +jest.useFakeTimers(); + +// Mock the the Firebase Auth onAuthStateChanged function, which pauses for 1 +// second before returning a fake user with only a name field set. +const mockOnAuthStateChanged = jest.fn(cb => { + setTimeout(() => { + cb({ name: 'Keiffer' }) + }, 1000); +}); +jest.mock('firebase/app', () => { + return { + initializeApp: () => { + return { + auth: () => { + return { + onAuthStateChanged: mockOnAuthStateChanged + } + } + } + } + } +}); + +describe('AuthProvider component', () => { + beforeEach(() => { + render(); + }); + + afterEach(() => { + cleanup(); + }); + + it('initially displays "Loading"', () => { + act(() => jest.advanceTimersByTime(500)); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); + + it('returns a provider when onAuthStateChanged is called', () => { + act(() => jest.advanceTimersByTime(2000)); + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + }); +}); + +describe('AuthContext Consumer component', () => { + // A Consumer component for AuthContext that just displays the current + // user. + const TestAuthContextConsumerComponent = () => { + const {currentUser} = useContext(AuthContext); + + return ( +
+
{currentUser.name}
+
+ ); + } + + beforeEach(() => { + render( + + + + ); + }) + + afterEach(() => { + cleanup(); + }) + + it('initially displays "Loading"', () => { + act(() => jest.advanceTimersByTime(500)); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); + + it('displays the current user when they are authenticated', () => { + act(() => jest.advanceTimersByTime(2000)); + expect(screen.getByText('Keiffer')).toBeInTheDocument(); + }) +}); From 8c478340b1ad98b747e54fc69ade67e1eea93f2d Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 04:06:18 -0400 Subject: [PATCH 09/19] Clarify variable --- frontend/src/components/Auth/AuthContext.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Auth/AuthContext.test.js b/frontend/src/components/Auth/AuthContext.test.js index a54cb00b..7e9bb751 100644 --- a/frontend/src/components/Auth/AuthContext.test.js +++ b/frontend/src/components/Auth/AuthContext.test.js @@ -6,9 +6,9 @@ jest.useFakeTimers(); // Mock the the Firebase Auth onAuthStateChanged function, which pauses for 1 // second before returning a fake user with only a name field set. -const mockOnAuthStateChanged = jest.fn(cb => { +const mockOnAuthStateChanged = jest.fn(callback => { setTimeout(() => { - cb({ name: 'Keiffer' }) + callback({ name: 'Keiffer' }) }, 1000); }); jest.mock('firebase/app', () => { From 63667867995eda8f611b5fccfecab0e3898f461f Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 04:12:04 -0400 Subject: [PATCH 10/19] Fix import --- frontend/src/components/Auth/AuthContext.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Auth/AuthContext.test.js b/frontend/src/components/Auth/AuthContext.test.js index 7e9bb751..a7ae1a9f 100644 --- a/frontend/src/components/Auth/AuthContext.test.js +++ b/frontend/src/components/Auth/AuthContext.test.js @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import { render, act, screen, cleanup } from '@testing-library/react'; -import { AuthContext, AuthProvider } from './index'; +import { AuthContext, AuthProvider } from './AuthContext.js'; jest.useFakeTimers(); From 3e6c3290e6165e423c7e335e1327418a1bf05bc2 Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 05:02:34 -0400 Subject: [PATCH 11/19] Clean test file --- frontend/src/components/Auth/AuthContext.test.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/Auth/AuthContext.test.js b/frontend/src/components/Auth/AuthContext.test.js index a7ae1a9f..194e1301 100644 --- a/frontend/src/components/Auth/AuthContext.test.js +++ b/frontend/src/components/Auth/AuthContext.test.js @@ -26,13 +26,9 @@ jest.mock('firebase/app', () => { }); describe('AuthProvider component', () => { - beforeEach(() => { - render(); - }); + beforeEach(() => { render() }); - afterEach(() => { - cleanup(); - }); + afterEach(cleanup); it('initially displays "Loading"', () => { act(() => jest.advanceTimersByTime(500)); @@ -56,7 +52,7 @@ describe('AuthContext Consumer component', () => {
{currentUser.name}
); - } + }; beforeEach(() => { render( @@ -64,11 +60,11 @@ describe('AuthContext Consumer component', () => {
); - }) + }); afterEach(() => { cleanup(); - }) + }); it('initially displays "Loading"', () => { act(() => jest.advanceTimersByTime(500)); @@ -78,5 +74,5 @@ describe('AuthContext Consumer component', () => { it('displays the current user when they are authenticated', () => { act(() => jest.advanceTimersByTime(2000)); expect(screen.getByText('Keiffer')).toBeInTheDocument(); - }) + }); }); From 3370ed1f716fdd8a0c3fd0694b5739d56565d188 Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 05:03:51 -0400 Subject: [PATCH 12/19] Install history module for testing --- frontend/package-lock.json | 41 +++++++++++++++++++++++++++++--------- frontend/package.json | 1 + 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 75aa4858..aa16d2a6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -6509,16 +6509,11 @@ "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==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.0.0.tgz", + "integrity": "sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==", "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" + "@babel/runtime": "^7.7.6" } }, "hmac-drbg": { @@ -11001,6 +10996,19 @@ "tiny-warning": "^1.0.0" }, "dependencies": { + "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" + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -11028,6 +11036,21 @@ "react-router": "5.2.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" + }, + "dependencies": { + "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" + } + } } }, "react-scripts": { diff --git a/frontend/package.json b/frontend/package.json index eb26b516..7ccde1a7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "@testing-library/user-event": "^7.2.1", "bootstrap": "^4.5.0", "firebase": "^7.15.5", + "history": "^5.0.0", "react": "^16.13.1", "react-bootstrap": "^1.0.1", "react-dom": "^16.13.1", From 2702dd00e2519f821129a9efc1d19bbbbce1943e Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 05:41:10 -0400 Subject: [PATCH 13/19] Add more tests --- frontend/src/components/Auth/AuthContext.js | 2 +- .../src/components/Auth/AuthContext.test.js | 2 +- frontend/src/components/Auth/PrivateRoute.js | 2 +- .../src/components/Auth/PrivateRoute.test.js | 48 +++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/Auth/PrivateRoute.test.js diff --git a/frontend/src/components/Auth/AuthContext.js b/frontend/src/components/Auth/AuthContext.js index 7c6b6b03..561967f6 100644 --- a/frontend/src/components/Auth/AuthContext.js +++ b/frontend/src/components/Auth/AuthContext.js @@ -25,7 +25,7 @@ const AuthProvider = (props) => { } return ( - + {props.children} ); diff --git a/frontend/src/components/Auth/AuthContext.test.js b/frontend/src/components/Auth/AuthContext.test.js index 194e1301..d768dfac 100644 --- a/frontend/src/components/Auth/AuthContext.test.js +++ b/frontend/src/components/Auth/AuthContext.test.js @@ -45,7 +45,7 @@ describe('AuthContext Consumer component', () => { // A Consumer component for AuthContext that just displays the current // user. const TestAuthContextConsumerComponent = () => { - const {currentUser} = useContext(AuthContext); + const currentUser = useContext(AuthContext); return (
diff --git a/frontend/src/components/Auth/PrivateRoute.js b/frontend/src/components/Auth/PrivateRoute.js index 0295d7b6..8a86bafa 100644 --- a/frontend/src/components/Auth/PrivateRoute.js +++ b/frontend/src/components/Auth/PrivateRoute.js @@ -11,7 +11,7 @@ import { SIGN_IN } from '../../constants/routes.js'; * will be redirected to the SIGN_IN page. */ const PrivateRoute = ({ component: RouteComponent, ...rest }) => { - const {currentUser} = useContext(AuthContext); + const currentUser = useContext(AuthContext); return ( ( + { + ...(jest.requireActual('react')), + useContext: jest + .fn() + .mockReturnValueOnce(null) + .mockReturnValueOnce({ name: 'Keiffer' }) + } +)); + +describe('PrivateRoute component', () => { + const TestComponent = () => { + return ( +
Hello, World!
+ ); + }; + + beforeEach(() => { + render( + + +
{SIGN_IN}
} /> +
+ ); + }); + + afterEach(cleanup); + + // mockOnAuthStateChanged called first time, so user should be null. + it('redirects to SIGN_IN when the user is not authenticated', () => { + expect(history.location.pathname).toEqual(SIGN_IN); + }); + + // mockOnAuthStateChanged called second time, so user should not be null. + it('renders the given component when the user is authenticated', () => { + expect(screen.getByText('Hello, World!')).toBeInTheDocument(); + }); +}); From d5562234d5942fdde5e6299b5ddfd21ae702f57e Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 05:42:18 -0400 Subject: [PATCH 14/19] Clean test --- frontend/src/components/Auth/PrivateRoute.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Auth/PrivateRoute.test.js b/frontend/src/components/Auth/PrivateRoute.test.js index 7a90787c..9051c472 100644 --- a/frontend/src/components/Auth/PrivateRoute.test.js +++ b/frontend/src/components/Auth/PrivateRoute.test.js @@ -29,7 +29,7 @@ describe('PrivateRoute component', () => { render( -
{SIGN_IN}
} /> +
); }); From e290972f6497694424d02b1ce481faf4607571bd Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 12:28:45 -0400 Subject: [PATCH 15/19] Add comment --- frontend/src/components/Auth/PrivateRoute.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/Auth/PrivateRoute.test.js b/frontend/src/components/Auth/PrivateRoute.test.js index 9051c472..a01b1c9b 100644 --- a/frontend/src/components/Auth/PrivateRoute.test.js +++ b/frontend/src/components/Auth/PrivateRoute.test.js @@ -8,6 +8,8 @@ import { SIGN_IN } from '../../constants/routes.js'; const history = createMemoryHistory(); +// Mock the useContext function so that, when called in the PrivateRoute +// component, returns null the first time and a fake user the second time. jest.mock('react', () => ( { ...(jest.requireActual('react')), From 8aecb0ff1a7df7a3b86db8fed05b0cb3b7445fd1 Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Fri, 10 Jul 2020 12:43:30 -0400 Subject: [PATCH 16/19] Add TODO --- frontend/src/components/Auth/AuthContext.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/Auth/AuthContext.js b/frontend/src/components/Auth/AuthContext.js index 561967f6..b3a1a00a 100644 --- a/frontend/src/components/Auth/AuthContext.js +++ b/frontend/src/components/Auth/AuthContext.js @@ -21,6 +21,8 @@ const AuthProvider = (props) => { }, []); if (isLoading) { + // TODO (Issue #25): Page initially displays "Loading..." for testing + // purposes, make this blank in the deployed build. return (

Loading...

); } From d0aa3386ae71d8eae7f1eb0007f025ead044071b Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Mon, 13 Jul 2020 12:22:11 -0400 Subject: [PATCH 17/19] Make constants for magic numbers and clean some of the structure --- .../src/components/Auth/AuthContext.test.js | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/Auth/AuthContext.test.js b/frontend/src/components/Auth/AuthContext.test.js index d768dfac..4783a85e 100644 --- a/frontend/src/components/Auth/AuthContext.test.js +++ b/frontend/src/components/Auth/AuthContext.test.js @@ -4,12 +4,18 @@ import { AuthContext, AuthProvider } from './AuthContext.js'; jest.useFakeTimers(); -// Mock the the Firebase Auth onAuthStateChanged function, which pauses for 1 -// second before returning a fake user with only a name field set. +// All times are in milliseconds. +const TIME_BEFORE_USER_IS_LOADED = 500; +const TIME_WHEN_USER_IS_LOADED = 1000; +const TIME_AFTER_USER_IS_LOADED = 2000; + +// Mock the the Firebase Auth onAuthStateChanged function, which pauses for the +// time given by TIME_WHEN_USER_IS_LOADED, then returns a fake user with only +// the property `name: 'Keiffer'`. const mockOnAuthStateChanged = jest.fn(callback => { setTimeout(() => { callback({ name: 'Keiffer' }) - }, 1000); + }, TIME_WHEN_USER_IS_LOADED); }); jest.mock('firebase/app', () => { return { @@ -25,18 +31,18 @@ jest.mock('firebase/app', () => { } }); +afterEach(cleanup); + describe('AuthProvider component', () => { beforeEach(() => { render() }); - afterEach(cleanup); - it('initially displays "Loading"', () => { - act(() => jest.advanceTimersByTime(500)); + act(() => jest.advanceTimersByTime(TIME_BEFORE_USER_IS_LOADED)); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); it('returns a provider when onAuthStateChanged is called', () => { - act(() => jest.advanceTimersByTime(2000)); + act(() => jest.advanceTimersByTime(TIME_AFTER_USER_IS_LOADED)); expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); }); @@ -62,17 +68,13 @@ describe('AuthContext Consumer component', () => { ); }); - afterEach(() => { - cleanup(); - }); - it('initially displays "Loading"', () => { - act(() => jest.advanceTimersByTime(500)); + act(() => jest.advanceTimersByTime(TIME_BEFORE_USER_IS_LOADED)); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); it('displays the current user when they are authenticated', () => { - act(() => jest.advanceTimersByTime(2000)); + act(() => jest.advanceTimersByTime(TIME_AFTER_USER_IS_LOADED)); expect(screen.getByText('Keiffer')).toBeInTheDocument(); }); }); From 2930d3d5b22ce3c367cbd079fa569cc0bdd63199 Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Tue, 14 Jul 2020 13:14:10 -0400 Subject: [PATCH 18/19] Add tripId URL param --- frontend/src/components/App/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/App/index.js b/frontend/src/components/App/index.js index 544d9c35..e443ad03 100644 --- a/frontend/src/components/App/index.js +++ b/frontend/src/components/App/index.js @@ -21,7 +21,7 @@ class App extends React.Component { - +
From beecb6b8a783942d34197c03ee04b2fe61662008 Mon Sep 17 00:00:00 2001 From: Keiffer Acoba Date: Mon, 20 Jul 2020 18:19:14 -0400 Subject: [PATCH 19/19] Add comments --- frontend/src/components/Auth/PrivateRoute.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Auth/PrivateRoute.js b/frontend/src/components/Auth/PrivateRoute.js index 8a86bafa..c06818e1 100644 --- a/frontend/src/components/Auth/PrivateRoute.js +++ b/frontend/src/components/Auth/PrivateRoute.js @@ -5,14 +5,26 @@ import { AuthContext } from '../Auth'; import { SIGN_IN } from '../../constants/routes.js'; /** - * PrivateRoute component that takes the authentication status of the current - * user from AuthContext. If they are authenticated, they will be allowed to - * view the contents of the Route component. If they are not authenticated, they - * will be redirected to the SIGN_IN page. + * PrivateRoute component that functions similarly to the `Route` component, + * with the added check that determines if the user is currently signed in. + * + * This component takes the authentication status of the current user from + * AuthContext. If they are authenticated, they will be allowed to view the + * contents of the Route component. If they are not authenticated, they will be + * redirected to the SIGN_IN page. + * + * @param {Object} props The following props are expected: + * - component {React.Component} The component that `PrivateRoute` should render + * if the user is currently authenticated. */ const PrivateRoute = ({ component: RouteComponent, ...rest }) => { const currentUser = useContext(AuthContext); + // The rest of the props passed into `PrivateRoute` are given as props to the + // `Route` component as normal. The render prop is used to specify that, if + // the user is signed in, the given component to render should be rendered + // along with all the standard Route paths (URL path, etc.), and if the user + // is not signed in, a `Redirect` prop should be rendered instead. return (