refactor(site): SignInForm without wrapper component (#558)

* Remove wrapper from SignInForm

* Spruce up tests

* Add util for form props

* Add back trim

* Add unit tests

* Lint, type fixes

* Pascal case for language

* Arrow functions

* Target text in e2e
This commit is contained in:
Presley Pizzo
2022-03-30 13:08:37 -04:00
committed by GitHub
parent f4ac7a3709
commit 7e48df8cb5
7 changed files with 164 additions and 57 deletions
+74
View File
@@ -0,0 +1,74 @@
import { FormikContextType } from "formik/dist/types"
import { getFormHelpers, onChangeTrimmed } from "./index"
interface TestType {
untouchedGoodField: string
untouchedBadField: string
touchedGoodField: string
touchedBadField: string
}
const mockHandleChange = jest.fn()
const form = {
errors: {
untouchedGoodField: undefined,
untouchedBadField: "oops!",
touchedGoodField: undefined,
touchedBadField: "oops!",
},
touched: {
untouchedGoodField: false,
untouchedBadField: false,
touchedGoodField: true,
touchedBadField: true,
},
handleChange: mockHandleChange,
handleBlur: jest.fn(),
getFieldProps: (name: string) => {
return {
name,
onBlur: jest.fn(),
onChange: jest.fn(),
value: "",
}
},
} as unknown as FormikContextType<TestType>
describe("form util functions", () => {
describe("getFormHelpers", () => {
const untouchedGoodResult = getFormHelpers<TestType>(form, "untouchedGoodField")
const untouchedBadResult = getFormHelpers<TestType>(form, "untouchedBadField")
const touchedGoodResult = getFormHelpers<TestType>(form, "touchedGoodField")
const touchedBadResult = getFormHelpers<TestType>(form, "touchedBadField")
it("populates the 'field props'", () => {
expect(untouchedGoodResult.name).toEqual("untouchedGoodField")
expect(untouchedGoodResult.onBlur).toBeDefined()
expect(untouchedGoodResult.onChange).toBeDefined()
expect(untouchedGoodResult.value).toBeDefined()
})
it("sets the id to the name", () => {
expect(untouchedGoodResult.id).toEqual("untouchedGoodField")
})
it("sets error to true if touched and invalid", () => {
expect(untouchedGoodResult.error).toBeFalsy
expect(untouchedBadResult.error).toBeFalsy
expect(touchedGoodResult.error).toBeFalsy
expect(touchedBadResult.error).toBeTruthy
})
it("sets helperText to the error message if touched and invalid", () => {
expect(untouchedGoodResult.helperText).toBeUndefined
expect(untouchedBadResult.helperText).toBeUndefined
expect(touchedGoodResult.helperText).toBeUndefined
expect(touchedBadResult.helperText).toEqual("oops!")
})
})
describe("onChangeTrimmed", () => {
it("calls handleChange with trimmed value", () => {
const event = { target: { value: " hello " } } as React.ChangeEvent<HTMLInputElement>
onChangeTrimmed<TestType>(form)(event)
expect(mockHandleChange).toHaveBeenCalledWith({ target: { value: "hello" } })
})
})
})
+32
View File
@@ -1,5 +1,37 @@
import { FormikContextType, getIn } from "formik"
import { ChangeEvent, ChangeEventHandler, FocusEventHandler } from "react"
export * from "./FormCloseButton"
export * from "./FormSection"
export * from "./FormDropdownField"
export * from "./FormTextField"
export * from "./FormTitle"
interface FormHelpers {
name: string
onBlur: FocusEventHandler
onChange: ChangeEventHandler
id: string
value?: string | number
error: boolean
helperText?: string
}
export const getFormHelpers = <T>(form: FormikContextType<T>, name: string): FormHelpers => {
// getIn is a util function from Formik that gets at any depth of nesting, and is necessary for the types to work
const touched = getIn(form.touched, name)
const errors = getIn(form.errors, name)
return {
...form.getFieldProps(name),
id: name,
error: touched && Boolean(errors),
helperText: touched && errors,
}
}
export const onChangeTrimmed =
<T>(form: FormikContextType<T>) =>
(event: ChangeEvent<HTMLInputElement>): void => {
event.target.value = event.target.value.trim()
form.handleChange(event)
}
+35 -45
View File
@@ -4,9 +4,10 @@ import React from "react"
import * as Yup from "yup"
import { Welcome } from "./Welcome"
import { FormTextField } from "../Form"
import FormHelperText from "@material-ui/core/FormHelperText"
import { LoadingButton } from "./../Button"
import TextField from "@material-ui/core/TextField"
import { getFormHelpers, onChangeTrimmed } from "../Form"
/**
* BuiltInAuthFormValues describes a form using built-in (email/password)
@@ -18,8 +19,17 @@ interface BuiltInAuthFormValues {
password: string
}
export const Language = {
emailLabel: "Email",
passwordLabel: "Password",
emailInvalid: "Please enter a valid email address.",
emailRequired: "Please enter an email address.",
authErrorMessage: "Incorrect email or password.",
signIn: "Sign In",
}
const validationSchema = Yup.object({
email: Yup.string().required("Email is required."),
email: Yup.string().trim().email(Language.emailInvalid).required(Language.emailRequired),
password: Yup.string(),
})
@@ -59,50 +69,30 @@ export const SignInForm: React.FC<SignInFormProps> = ({ isLoading, authErrorMess
<>
<Welcome />
<form onSubmit={form.handleSubmit}>
<div>
<FormTextField
label="Email"
autoComplete="email"
autoFocus
className={styles.loginTextField}
eventTransform={(email: string) => email.trim()}
form={form}
formFieldName="email"
fullWidth
inputProps={{
id: "signin-form-inpt-email",
}}
variant="outlined"
/>
<FormTextField
label="Password"
autoComplete="current-password"
className={styles.loginTextField}
form={form}
formFieldName="password"
fullWidth
inputProps={{
id: "signin-form-inpt-password",
}}
isPassword
variant="outlined"
/>
{authErrorMessage && (
<FormHelperText data-testid="sign-in-error" error>
{authErrorMessage}
</FormHelperText>
)}
</div>
<TextField
{...getFormHelpers<BuiltInAuthFormValues>(form, "email")}
onChange={onChangeTrimmed(form)}
autoFocus
autoComplete="email"
className={styles.loginTextField}
fullWidth
label={Language.emailLabel}
variant="outlined"
/>
<TextField
{...getFormHelpers<BuiltInAuthFormValues>(form, "password")}
autoComplete="current-password"
className={styles.loginTextField}
fullWidth
id="password"
label={Language.passwordLabel}
type="password"
variant="outlined"
/>
{authErrorMessage && <FormHelperText error>{Language.authErrorMessage}</FormHelperText>}
<div className={styles.submitBtn}>
<LoadingButton
color="primary"
loading={isLoading}
fullWidth
id="signin-form-submit"
type="submit"
variant="contained"
>
{isLoading ? "" : "Sign In"}
<LoadingButton color="primary" loading={isLoading} fullWidth type="submit" variant="contained">
{isLoading ? "" : Language.signIn}
</LoadingButton>
</div>
</form>