- Notifications
You must be signed in to change notification settings - Fork928
refactor(site): SignInForm without wrapper component#558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
b3bdf83
d2741c3
918dd80
73df2da
e1e12d8
108c77e
50e6e0c
17bc113
c5619f8
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" } }) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -4,9 +4,10 @@ import React from "react" | ||
import * as Yup from "yup" | ||
import { Welcome } from "./Welcome" | ||
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().trim().email(Language.emailInvalid).required(Language.emailRequired), | ||
password: Yup.string(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. @vapurrmaid@kylecarbs do we want validation on the password? Contributor
| ||
}) | ||
@@ -59,50 +69,30 @@ export const SignInForm: React.FC<SignInFormProps> = ({ isLoading, authErrorMess | ||
<> | ||
<Welcome /> | ||
<form onSubmit={form.handleSubmit}> | ||
<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 type="submit" variant="contained"> | ||
{isLoading ? "" : Language.signIn} | ||
</LoadingButton> | ||
</div> | ||
</form> | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
import React from "react" | ||
import { act, screen } from "@testing-library/react" | ||
import userEvent from "@testing-library/user-event" | ||
import { history, render } from "../test_helpers" | ||
import { SignInPage } from "./login" | ||
import { server } from "../test_helpers/server" | ||
import { rest } from "msw" | ||
import { Language } from "../components/SignIn/SignInForm" | ||
describe("SignInPage", () => { | ||
beforeEach(() => { | ||
@@ -21,12 +23,12 @@ describe("SignInPage", () => { | ||
render(<SignInPage />) | ||
// Then | ||
await screen.findByText(Language.signIn, { exact: false }) | ||
}) | ||
it("shows an error message if SignIn fails", async () => { | ||
// Given | ||
render(<SignInPage />) | ||
// Make login fail | ||
server.use( | ||
rest.post("/api/v2/users/login", async (req, res, ctx) => { | ||
@@ -35,17 +37,18 @@ describe("SignInPage", () => { | ||
) | ||
// When | ||
// Set email / password | ||
const email = screen.getByLabelText(Language.emailLabel) | ||
const password = screen.getByLabelText(Language.passwordLabel) | ||
userEvent.type(email, "test@coder.com") | ||
userEvent.type(password, "password") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. praise: This is looking really nice! | ||
// Click sign-in | ||
const signInButton = await screen.findByText(Language.signIn) | ||
act(() => signInButton.click()) | ||
// Then | ||
// Finding error by test id because it comes from the backend | ||
const errorMessage = await screen.findByText(Language.authErrorMessage) | ||
expect(errorMessage).toBeDefined() | ||
expect(history.location.pathname).toEqual("/login") | ||
}) | ||