feat(fe-login): redirect on successful login + provide error feedback on error

This commit is contained in:
Marc 2023-12-28 15:40:35 -05:00
parent 53d43ff070
commit 8a70c144d5
Signed by: marc
GPG key ID: 048E042F22B5DC79
5 changed files with 107 additions and 9 deletions

View file

@ -27,6 +27,7 @@
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.5.3",
"@types/node": "^20.10.6",
"@types/react": "^18.2.18",
"@types/react-dom": "^18.2.7",
"axios-mock-adapter": "^1.21.5",

View file

@ -1,9 +1,10 @@
import { render, screen } from "@testing-library/react"
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"
import AxiosMockAdapter from "axios-mock-adapter"
import axios from "../../axios"
import * as locationHook from "../../contexts/LocationContext"
import LoginView from "."
function renderComponent() {
@ -54,7 +55,7 @@ describe("LoginView", () => {
it("sends a request to the authentication API on submit", async () => {
const axiosMockAdapter = new AxiosMockAdapter(axios)
axiosMockAdapter.onPost("/auth/session/").reply(201, { token: "testtoken" })
axiosMockAdapter.onPost("/auth/session/").reply(201)
const { user } = renderComponent()
@ -79,4 +80,72 @@ describe("LoginView", () => {
expect(requestBody).toEqual(testInput)
})
it("displays error messaging if the login attempt is not successful", async () => {
const axiosMockAdapter = new AxiosMockAdapter(axios)
axiosMockAdapter.onPost("/auth/session/").reply(400)
const { user } = renderComponent()
const testInput = {
username: "test@domain.com",
password: "password",
}
const emailInput = screen.getByLabelText(/email address login input/i)
await user.type(emailInput, testInput.username)
const passwordInput = screen.getByLabelText(/password login input/i)
await user.type(passwordInput, testInput.password)
const submitButton = screen.getByText("Log in", { selector: "button" })
await user.click(submitButton)
await waitFor(() => expect(screen.getByRole("alert")).toBeInTheDocument())
expect(
screen.getByText(
/this combination of email and password does not match our records\. verify if the email or password are incorrect\./i,
),
).toBeInTheDocument()
})
it("redirects the user on success", async () => {
const mockNavigate = jest.fn()
const mockLocationHook = jest
.spyOn(locationHook, "useLocationContext")
.mockImplementation(() => ({
location: {
path: "",
label: "",
params: {},
pattern: "",
},
navigate: mockNavigate,
}))
const axiosMockAdapter = new AxiosMockAdapter(axios)
axiosMockAdapter.onPost("/auth/session/").reply(201)
const { user } = renderComponent()
const testInput = {
username: "test@domain.com",
password: "password",
}
const emailInput = screen.getByLabelText(/email address login input/i)
await user.type(emailInput, testInput.username)
const passwordInput = screen.getByLabelText(/password login input/i)
await user.type(passwordInput, testInput.password)
const submitButton = screen.getByText("Log in", { selector: "button" })
await user.click(submitButton)
expect(mockNavigate).toHaveBeenCalledWith("/")
})
})

View file

@ -9,25 +9,29 @@ import TextField from "@mui/material/TextField"
import InputLabel from "@mui/material/InputLabel"
import Button from "@mui/material/Button"
import Link from "@mui/material/Link"
import Alert from "@mui/material/Alert"
import axiosWithDefaults from "../../axios"
import TextInput from "../TextInput"
import { useLocationContext } from "../../contexts/LocationContext"
function LoginView() {
const [emailAddress, setEmailAddress] = React.useState<string>("")
const [password, setPassword] = React.useState<string>("")
const { navigate } = useLocationContext()
const { mutate } = useMutation({
const { mutate, isError, isLoading } = useMutation({
mutationFn: async ({
email,
password,
}: { email: string; password: string }) => {
const response = await axiosWithDefaults.post("/auth/session/", {
return axiosWithDefaults.post("/auth/session/", {
username: email,
password,
})
return response
},
onSuccess: (response) => {
navigate("/")
},
})
@ -71,22 +75,29 @@ function LoginView() {
<Box sx={{ display: "flex", justifyContent: "center", width: "100%" }}>
<FormGroup
sx={{
flexGrow: 0.1,
display: "flex",
gap: "10px",
textAlign: "center",
width: "600px",
}}
>
<Typography variant="h1" sx={{ fontSize: "2rem" }}>
Log in
</Typography>
{isError ? (
<Alert severity="error">
{
"This combination of email and password does not match our records. Verify if the email or password are incorrect."
}
</Alert>
) : null}
{emailField}
{passwordField}
<Button
variant="contained"
onClick={onLoginClick}
aria-label="submit login"
disabled={!isFormValid}
disabled={!isFormValid || isLoading}
>
Log in
</Button>

View file

@ -11,6 +11,6 @@
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true,
"types": ["jest"]
"types": ["jest", "node"]
}
}

View file

@ -2440,6 +2440,15 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^20.10.6":
version: 20.10.6
resolution: "@types/node@npm:20.10.6"
dependencies:
undici-types: "npm:~5.26.4"
checksum: 08471220d3cbbb6669835c4b78541edf5eface8f2c2e36c550cfa4ff73da73071c90e200a06359fac25d6564127597c23e178128058fb676824ec23d5178a017
languageName: node
linkType: hard
"@types/parse-json@npm:^4.0.0":
version: 4.0.0
resolution: "@types/parse-json@npm:4.0.0"
@ -6453,6 +6462,7 @@ __metadata:
"@testing-library/react": "npm:^14.1.2"
"@testing-library/user-event": "npm:^14.5.1"
"@types/jest": "npm:^29.5.3"
"@types/node": "npm:^20.10.6"
"@types/react": "npm:^18.2.18"
"@types/react-dom": "npm:^18.2.7"
axios: "npm:^1.4.0"
@ -7001,6 +7011,13 @@ __metadata:
languageName: node
linkType: hard
"undici-types@npm:~5.26.4":
version: 5.26.5
resolution: "undici-types@npm:5.26.5"
checksum: 0097779d94bc0fd26f0418b3a05472410408877279141ded2bd449167be1aed7ea5b76f756562cb3586a07f251b90799bab22d9019ceba49c037c76445f7cddd
languageName: node
linkType: hard
"unique-filename@npm:^3.0.0":
version: 3.0.0
resolution: "unique-filename@npm:3.0.0"