-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GCW-3432 Authentication routing (#6)
* GCW-3432 Remove routing logic * GCW-3432 Create component for main routing Essential for testing routing by allowing us to mock router history * GCW-3432 Move MainRouter to its own directory * GCW-3432 Test for presence of ion-router-outlet Ionic uses this to handle animations related to navigation/routing * GCW-3432 Move router outlet to MainRouter Component required by Ionic so rather not have to mock this out in tests * GCW-3432 Install history * GCW-3432 Test navigation to /home url * GCW-3432 Use Router instead of IonReactRouter Components do not show up as expected on JSDOM with IonReactRouter * GCW-3432 Show the home page * GCW-3432 Create login page and routing * GCW-3432 Refactor tests * GCW-3432 Remove test in incorrect location Test belongs to MainRouter * GCW-3432 Add missing assertion in test * GCW-3432 Add assertion for ion-router-outlet * GCW-3432 Make assertions more explicitly * GCW-3432 Create context for authentication state * GCW-3432 Route users based on auth state * GCW-3432 Downgrade history version Latest version has compatibility issues esp. redirects * GCW-3432 Test redirect normally without spyOn Reverting history ver. to 4 fixes the issue with redirects * GCW-3432 Redirect user from root path Routing should be based on user auth status * GCW-3432 Add title to login page * GCW-3432 Test without resorting to data-testid Rely on actual DOM elements for testing rather than data-testid * GCW-3432 Change file naming convention index.ts convention makes it harder to distinguish tabs in editors * GCW-3432 Put Login into its own folder * GCW-3432 Move MainRouter to components folder * GCW-3432 Create login button * GCW-3432 Simplify test element selectors * GCW-3432 Create login functionality * GCW-3432 Update ion router outlet test Don't rely on data-testid * GCW-3432 Encapsulate auth access in custom hook * GCW-3432 Install TS + add type checking to tests Reference: facebook/create-react-app#5626 * GCW-3432 Encapsulate auth state in Provider * GCW-3432 Update tests to use AuthProvider * GCW-3432 Wrap app with AuthProvider * GCW-3432 Fix type error in test * GCW-3432 Refactor tests * GCW-3432 Refactor test expectations to function * GCW-3432 Refactor tests Change function signature of render function to allow for currying * GCW-3432 Refactor tests * GCW-3432 Route from non-existent page (unauthed) * GCW-3432 Route from non-existent page (authed) * GCW-3432 Remove redundant route * GCW-3432 Handle visiting page with /login prefix * GCW-3432 Handle visiting page with /home prefix * GCW-3432 Encapsulate private route logic * GCW-3432 Refactor login tests * GCW-3432 Redirect user to home on login * GCW-3432 Increase test clarity * GCW-3432 Move expectation fn to test utils
- Loading branch information
1 parent
f823bbf
commit dd7c56e
Showing
13 changed files
with
301 additions
and
18 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import React, { useState } from "react"; | ||
import AuthContext from "../context/AuthContext"; | ||
|
||
interface Props { | ||
initialAuthState?: boolean; | ||
} | ||
const AuthProvider: React.FC<Props> = ({ | ||
children, | ||
initialAuthState = false, | ||
}) => { | ||
const [isAuthenticated, setIsAuthenticated] = useState(initialAuthState); | ||
|
||
return ( | ||
<AuthContext.Provider value={{ isAuthenticated, setIsAuthenticated }}> | ||
{children} | ||
</AuthContext.Provider> | ||
); | ||
}; | ||
|
||
export default AuthProvider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import React from "react"; | ||
import { render } from "@testing-library/react"; | ||
import MainRouter from "./MainRouter"; | ||
import { createMemoryHistory } from "history"; | ||
import { Router } from "react-router"; | ||
import AuthProvider from "../../components/AuthProvider"; | ||
import { expectToBeOnPage } from "../../test-utils/matchers"; | ||
|
||
const renderComponent = (initialAuthState: boolean) => ( | ||
initialPath: string | ||
) => { | ||
const history = createMemoryHistory({ initialEntries: [initialPath] }); | ||
return { | ||
history, | ||
...render( | ||
<AuthProvider initialAuthState={initialAuthState}> | ||
<Router history={history}> | ||
<MainRouter /> | ||
</Router> | ||
</AuthProvider> | ||
), | ||
}; | ||
}; | ||
|
||
describe("Unauthenticated User", () => { | ||
const renderUnauthenticatedComponent = renderComponent(false); | ||
|
||
[ | ||
{ initialPath: "/home", expectedPage: "login" }, | ||
{ initialPath: "/login", expectedPage: "login" }, | ||
{ initialPath: "/", expectedPage: "login" }, | ||
{ initialPath: "/bad-route", expectedPage: "login" }, | ||
{ initialPath: "/home/bad-route", expectedPage: "login" }, | ||
{ initialPath: "/login/bad-route", expectedPage: "login" }, | ||
].map(({ initialPath, expectedPage }) => { | ||
it(`visiting ${initialPath} should be taken to ${expectedPage}`, () => { | ||
const { container, history } = renderUnauthenticatedComponent( | ||
initialPath | ||
); | ||
expectToBeOnPage(container, history.location.pathname, expectedPage); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("Authenticated User", () => { | ||
const renderAuthenticatedComponent = renderComponent(true); | ||
|
||
[ | ||
{ initialPath: "/home", expectedPage: "home" }, | ||
{ initialPath: "/login", expectedPage: "login" }, | ||
{ initialPath: "/", expectedPage: "home" }, | ||
{ initialPath: "/bad-route", expectedPage: "home" }, | ||
{ initialPath: "/home/bad-route", expectedPage: "home" }, | ||
{ initialPath: "/login/bad-route", expectedPage: "home" }, | ||
].map(({ initialPath, expectedPage }) => { | ||
it(`visiting ${initialPath} should be taken to ${expectedPage}`, () => { | ||
const { container, history } = renderAuthenticatedComponent(initialPath); | ||
expectToBeOnPage(container, history.location.pathname, expectedPage); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from "react"; | ||
import { Redirect, Route, Switch } from "react-router"; | ||
import Home from "../../pages/Home"; | ||
import Login from "../../pages/Login/Login"; | ||
import useAuth from "../../hooks/useAuth"; | ||
import PrivateRoute from "../../components/PrivateRoute"; | ||
|
||
const MainRouter: React.FC = () => { | ||
const { isAuthenticated } = useAuth(); | ||
|
||
return ( | ||
<Switch> | ||
<Route exact path="/login"> | ||
<Login /> | ||
</Route> | ||
<PrivateRoute exact path="/home"> | ||
<Home /> | ||
</PrivateRoute> | ||
<Redirect to={isAuthenticated ? "/home" : "/login"} /> | ||
</Switch> | ||
); | ||
}; | ||
|
||
export default MainRouter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from "react"; | ||
import { Redirect, Route } from "react-router"; | ||
import useAuth from "../hooks/useAuth"; | ||
|
||
interface RestProps { | ||
exact: boolean; | ||
path: string; | ||
} | ||
const PrivateRoute: React.FC<RestProps> = ({ children, ...rest }) => { | ||
const { isAuthenticated } = useAuth(); | ||
|
||
return ( | ||
<Route {...rest}> | ||
{isAuthenticated ? children : <Redirect to="/login" />} | ||
</Route> | ||
); | ||
}; | ||
|
||
export default PrivateRoute; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import React from "react"; | ||
|
||
interface ContextProps { | ||
isAuthenticated: boolean; | ||
setIsAuthenticated: React.Dispatch<React.SetStateAction<boolean>>; | ||
} | ||
|
||
export const AuthContext = React.createContext<ContextProps>({ | ||
isAuthenticated: false, | ||
setIsAuthenticated: () => {}, | ||
}); | ||
|
||
export default AuthContext; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { useContext } from "react"; | ||
import AuthContext from "../context/AuthContext"; | ||
|
||
const useAuth = () => useContext(AuthContext); | ||
|
||
export default useAuth; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import React from "react"; | ||
import { render } from "@testing-library/react"; | ||
import Login from "./Login"; | ||
import userEvent, { TargetElement } from "@testing-library/user-event"; | ||
import AuthContext from "../../context/AuthContext"; | ||
import AuthProvider from "../../components/AuthProvider"; | ||
import ReactRouter, { MemoryRouter } from "react-router"; | ||
|
||
test("renders a login title", () => { | ||
const { container } = render(<Login />); | ||
expect(container.querySelector("ion-title")).toHaveTextContent(/login/i); | ||
}); | ||
|
||
test("renders a login button", () => { | ||
const { container } = render(<Login />); | ||
expect(container.querySelector("ion-button")).toHaveTextContent(/login/i); | ||
}); | ||
|
||
test("clicking the login button logs user in", () => { | ||
const mockSetIsAuthenticated = jest.fn(); | ||
const { container } = render( | ||
<AuthContext.Provider | ||
value={{ | ||
isAuthenticated: false, | ||
setIsAuthenticated: mockSetIsAuthenticated, | ||
}} | ||
> | ||
<MemoryRouter initialEntries={["/login"]}> | ||
<Login /> | ||
</MemoryRouter> | ||
</AuthContext.Provider> | ||
); | ||
|
||
userEvent.click(container.querySelector("ion-button") as TargetElement); | ||
|
||
expect(mockSetIsAuthenticated).toHaveBeenCalledWith(true); | ||
expect(mockSetIsAuthenticated).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test("user is redirected to home page on login", () => { | ||
const mockHistory = { replace: jest.fn() }; | ||
const mockUseHistory = jest | ||
.spyOn(ReactRouter, "useHistory") | ||
.mockReturnValue(mockHistory as any); | ||
|
||
const { container } = render( | ||
<AuthProvider> | ||
<MemoryRouter initialEntries={["/login"]}> | ||
<Login /> | ||
</MemoryRouter> | ||
</AuthProvider> | ||
); | ||
userEvent.click(container.querySelector("ion-button") as TargetElement); | ||
|
||
expect(mockHistory.replace).toHaveBeenCalledWith("/home"); | ||
|
||
mockUseHistory.mockRestore(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { | ||
IonButton, | ||
IonContent, | ||
IonHeader, | ||
IonPage, | ||
IonTitle, | ||
IonToolbar, | ||
} from "@ionic/react"; | ||
import React from "react"; | ||
import { useHistory } from "react-router"; | ||
import useAuth from "../../hooks/useAuth"; | ||
|
||
const Login: React.FC = () => { | ||
const { setIsAuthenticated } = useAuth(); | ||
const history = useHistory(); | ||
|
||
const handleLogin = () => { | ||
setIsAuthenticated(true); | ||
history.replace("/home"); | ||
}; | ||
|
||
return ( | ||
<IonPage title="login"> | ||
<IonHeader> | ||
<IonToolbar> | ||
<IonTitle>Login</IonTitle> | ||
</IonToolbar> | ||
</IonHeader> | ||
<IonContent> | ||
<IonButton onClick={handleLogin}>Login</IonButton> | ||
</IonContent> | ||
</IonPage> | ||
); | ||
}; | ||
|
||
export default Login; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
const expectToBeOnPage = ( | ||
container: HTMLElement, | ||
myPath: string, | ||
expectedPage: string | ||
) => { | ||
const expectedPath = `/${expectedPage}`; | ||
expect(myPath).toEqual(expectedPath); | ||
expect(container.querySelector(".ion-page")).toHaveAttribute( | ||
"title", | ||
expectedPage | ||
); | ||
}; | ||
|
||
export { expectToBeOnPage }; |