Skip to content

Commit

Permalink
RHEI/HHAG form field saves value on change
Browse files Browse the repository at this point in the history
  • Loading branch information
HelenHagos committed Sep 3, 2023
1 parent 5c31277 commit 6c0da5a
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 15 deletions.
69 changes: 61 additions & 8 deletions src/components/form/FormField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,22 @@ import { Observable } from "../../utilities/observable";
describe(FormField.name, () => {
const id = "id";
const name = "name";
const pageIdentifier = "my-page";
let textBox: HTMLInputElement;
let user: UserEvent;

beforeEach(() => {
user = userEvent.setup();
});

describe("when there is no validation error", () => {
afterEach(() => {
window.localStorage.clear();
});

describe("under normal circumstances - input", () => {
const errorMessage = "error message";
beforeEach(() => {
render(<FormField id={id} name={name} validator={() => errorMessage} autoComplete={"tel"}/>);
render(<FormField id={id} name={name} validator={() => errorMessage} autoComplete={"tel"} pageIdentifier={pageIdentifier}/>);
textBox = screen.getByRole("textbox");
});
test("shows an input", () => {
Expand All @@ -37,7 +42,17 @@ describe(FormField.name, () => {
test("passes autocomplete to the input", () => {
expect(textBox.autocomplete).toEqual("tel");
});
describe("and the field is blurred with an invalid value", () => {
test("writes to local storage when input changes", async() => {
const typedInValue = "AB";
await user.type(textBox, typedInValue);

expect(window.localStorage.getItem(`${pageIdentifier}-${id}`)).toEqual(typedInValue);

await user.type(textBox, "C");

expect(window.localStorage.getItem(`${pageIdentifier}-${id}`)).toEqual("ABC");
});
describe("when the field is blurred with an invalid value", () => {
beforeEach(async () => {
textBox.focus();
await user.tab();
Expand All @@ -47,9 +62,47 @@ describe(FormField.name, () => {
});
});
});
test("allows creating a textarea instead of an input", () => {
render(<FormField id={id} name={name} inputType={FormFieldType.TEXTAREA}/>);
expect(screen.getByRole("textbox")).toBeVisible();

describe("under normal circumstances - textarea", () => {
const errorMessage = "error message";
beforeEach(() => {
render(<FormField id={id} name={name} validator={() => errorMessage} inputType={FormFieldType.TEXTAREA} autoComplete={"tel"} pageIdentifier={pageIdentifier}/>);
textBox = screen.getByRole("textbox");
});
test("shows an input", () => {
expect(textBox).toBeVisible();
});
test("passes id to input", () => {
expect(textBox.id).toEqual(id);
});
test("passes name to input", () => {
expect(textBox.name).toEqual(name);
});
test("error message container is not present", () => {
expect(screen.queryByText(errorMessage)).not.toBeInTheDocument();
});
test("passes autocomplete to the input", () => {
expect(textBox.autocomplete).toEqual("tel");
});
test("writes to local storage when input changes", async() => {
const typedInValue = "AB";
await user.type(textBox, typedInValue);

expect(window.localStorage.getItem(`${pageIdentifier}-${id}`)).toEqual(typedInValue);

await user.type(textBox, "C");

expect(window.localStorage.getItem(`${pageIdentifier}-${id}`)).toEqual("ABC");
});
describe("when the field is blurred with an invalid value", () => {
beforeEach(async () => {
textBox.focus();
await user.tab();
});
test("error message container is not present", () => {
expect(screen.queryByText(errorMessage)).not.toBeInTheDocument();
});
});
});
describe("when there is a validation error", () => {
const invalidValue = "cheese";
Expand All @@ -63,7 +116,7 @@ describe(FormField.name, () => {
};

render(
<FormWithField id={id} name={name} validator={validator}/>,
<FormWithField id={id} name={name} validator={validator} pageIdentifier={pageIdentifier}/>,
);
textBox = screen.getByRole("textbox");
});
Expand Down Expand Up @@ -129,7 +182,7 @@ const FormWithField = (props: FormFieldProps) => {

return (<>
<FormProvider onSubmit={onSubmit}>
<FormField id={props.id} name={props.name} validator={props.validator}/>
<FormField id={props.id} name={props.name} validator={props.validator} pageIdentifier={props.pageIdentifier}/>
<button onClick={onClick}>SUBMIT</button>
</FormProvider>
</>);
Expand Down
13 changes: 10 additions & 3 deletions src/components/form/FormField.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { RefObject, useCallback, useContext, useEffect, useRef, useState } from "react";
import { useValidation, Validator } from "../../hooks/form-validation/useValidation";
import { FormContext } from "../../contexts/FormContext";
import { useLocalStorage } from "../../hooks/useLocalStorage";

export enum FormFieldType {
INPUT,
Expand All @@ -13,14 +14,16 @@ export type FormFieldProps = {
validator?: Validator
autoComplete?: string
inputType?: FormFieldType
pageIdentifier: string
}

export type FormFieldElement = HTMLInputElement | HTMLTextAreaElement;

export const FormField = ({ id, name, validator, autoComplete, inputType = FormFieldType.INPUT }: FormFieldProps) => {
export const FormField = ({ id, name, validator, autoComplete, pageIdentifier, inputType = FormFieldType.INPUT }: FormFieldProps) => {
const { registerValidation } = useContext(FormContext);
const inputRef = useRef<FormFieldElement>(null);
const [errorMessage, setErrorMessage] = useState<string>("");
const { save } = useLocalStorage(`${pageIdentifier}-${id}`);

const validation = useValidation({ validator, inputRef, setErrorMessage });

Expand All @@ -34,19 +37,23 @@ export const FormField = ({ id, name, validator, autoComplete, inputType = FormF
}
}, [validation, errorMessage]);

const onChange = useCallback((event) => {
save(event.target.value);
}, []);

const invalid = errorMessage !== "";
const errorMessageId = invalid ? `form-error-message-for-${id}` : "";

const input = inputType === FormFieldType.INPUT
? <input id={id} name={name} ref={inputRef as RefObject<HTMLInputElement>}
aria-describedby={errorMessageId}
aria-invalid={invalid} aria-errormessage={errorMessageId}
onBlur={onBlur} autoComplete={autoComplete} className={"form-input"}
onBlur={onBlur} onChange={onChange} autoComplete={autoComplete} className={"form-input"}
/>
: <textarea id={id} name={name} ref={inputRef as RefObject<HTMLTextAreaElement>}
aria-describedby={errorMessageId}
aria-invalid={invalid} aria-errormessage={errorMessageId}
onBlur={onBlur} autoComplete={autoComplete} className={"form-textarea"}
onBlur={onBlur} onChange={onChange} autoComplete={autoComplete} className={"form-textarea"}
/>;

const errorClass = invalid ? "invalid-form-field" : "";
Expand Down
10 changes: 9 additions & 1 deletion src/pages/accessibility-issues/AccessibilityIssues.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ACCESSIBILITY_PAGE_HEADING, AccessibilityFormData, AccessibilityIssues } from "./AccessibilityIssues";
import { ACCESSIBILITY_PAGE_HEADING, AccessibilityFormData, AccessibilityIssues, ACCESSIBILITY_PAGE_IDENTIFIER } from "./AccessibilityIssues";
import { render, screen, waitFor, within } from "@testing-library/react";
import React from "react";
import { rest } from "msw";
Expand Down Expand Up @@ -57,6 +57,14 @@ describe(`${AccessibilityIssues.name} form`, () => {
expect(screen.getByLabelText(ACCESSIBILITY_PAGE_HEADING)).toBe(form);
});

test("saves to local storage with correct key", async() => {
const nameInput: HTMLInputElement = within(form).getByRole("textbox", { name: "Your Name (optional)" });
const input: string = "Seth";
await user.type(nameInput, input);

expect(window.localStorage.getItem(`${ACCESSIBILITY_PAGE_IDENTIFIER}-name`)).toEqual(input);
});

describe("when the form data is valid", () => {
test("sends form contents to the server", async () => {
const nameInput: HTMLInputElement = within(form).getByRole("textbox", { name: "Your Name (optional)" });
Expand Down
9 changes: 8 additions & 1 deletion src/pages/accessibility-issues/AccessibilityIssues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type AccessibilityFormData = {
};

export const ACCESSIBILITY_PAGE_HEADING = 'Report Accessibility Issues';
export const ACCESSIBILITY_PAGE_IDENTIFIER = 'accessibility-issues';

export const AccessibilityIssues = () => {
const [successMessage] = useState(
Expand All @@ -34,12 +35,16 @@ export const AccessibilityIssues = () => {
<label htmlFor={'name'} className={'form-label'}>
Your Name (optional)
</label>
<FormField id={'name'} autoComplete={'name'} name={'name'} />
<FormField id={'name'}
pageIdentifier={ACCESSIBILITY_PAGE_IDENTIFIER}
autoComplete={'name'}
name={'name'} />
<label htmlFor={'email'} className={'form-label'}>
Your email (optional)
</label>
<FormField
id={'email'}
pageIdentifier={ACCESSIBILITY_PAGE_IDENTIFIER}
name={'email'}
validator={validateEmail}
autoComplete={'email'}
Expand All @@ -49,6 +54,7 @@ export const AccessibilityIssues = () => {
</label>
<FormField
id={'phone'}
pageIdentifier={ACCESSIBILITY_PAGE_IDENTIFIER}
name={'phone'}
validator={validatePhone}
autoComplete={'tel'}
Expand All @@ -58,6 +64,7 @@ export const AccessibilityIssues = () => {
</label>
<FormField
id={'description'}
pageIdentifier={ACCESSIBILITY_PAGE_IDENTIFIER}
name={'description'}
validator={validateRequired}
inputType={FormFieldType.TEXTAREA}
Expand Down
10 changes: 9 additions & 1 deletion src/pages/contact-us/ContactUs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { INVALID_EMAIL_MESSAGE } from "../../hooks/form-validation/validateEmail
import { INVALID_PHONE_MESSAGE } from "../../hooks/form-validation/validatePhone";
import { MISSING_REQUIRED_MESSAGE } from "../../hooks/form-validation/validateRequired";
import { Container } from "react-dom";
import { CONTACT_PAGE_HEADING, ContactFormData, ContactUs } from "./ContactUs";
import { CONTACT_PAGE_HEADING, ContactFormData, ContactUs, CONTACT_PAGE_IDENTIFIER } from "./ContactUs";

describe(`${ContactUs.name} form`, () => {
const formLabelText = "Contact Us";
Expand Down Expand Up @@ -58,6 +58,14 @@ describe(`${ContactUs.name} form`, () => {
expect(screen.getByLabelText(formLabelText)).toBe(form);
});

test("saves to local storage with correct key", async() => {
const nameInput: HTMLInputElement = within(form).getByRole("textbox", { name: "Your Name (optional)" });
const input: string = "Seth";
await user.type(nameInput, input);

expect(window.localStorage.getItem(`${CONTACT_PAGE_IDENTIFIER}-name`)).toEqual(input);
});

describe("when the form data is valid", () => {
test("sends form contents to the server", async () => {
const nameInput: HTMLInputElement = within(form).getByRole("textbox", { name: "Your Name (optional)" });
Expand Down
10 changes: 9 additions & 1 deletion src/pages/contact-us/ContactUs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type ContactFormData = {
};

export const CONTACT_PAGE_HEADING = 'Contact Us';
export const CONTACT_PAGE_IDENTIFIER = 'contact-us';

export const ContactUs = () => {
const [successMessage] = useState('Your message has been sent, thank you!');
Expand All @@ -34,12 +35,17 @@ export const ContactUs = () => {
<label htmlFor={'name'} className={'form-label'}>
Your Name (optional)
</label>
<FormField id={'name'} autoComplete={'name'} name={'name'} />
<FormField
id={'name'}
pageIdentifier={CONTACT_PAGE_IDENTIFIER}
autoComplete={'name'}
name={'name'} />
<label htmlFor={'email'} className={'form-label'}>
Your email (optional)
</label>
<FormField
id={'email'}
pageIdentifier={CONTACT_PAGE_IDENTIFIER}
name={'email'}
validator={validateEmail}
autoComplete={'email'}
Expand All @@ -49,6 +55,7 @@ export const ContactUs = () => {
</label>
<FormField
id={'phone'}
pageIdentifier={CONTACT_PAGE_IDENTIFIER}
name={'phone'}
validator={validatePhone}
autoComplete={'tel'}
Expand All @@ -58,6 +65,7 @@ export const ContactUs = () => {
</label>
<FormField
id={'description'}
pageIdentifier={CONTACT_PAGE_IDENTIFIER}
name={'description'}
validator={validateRequired}
inputType={FormFieldType.TEXTAREA}
Expand Down
8 changes: 8 additions & 0 deletions src/pages/page-identifiers.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ACCESSIBILITY_PAGE_IDENTIFIER } from "./accessibility-issues/AccessibilityIssues";
import { CONTACT_PAGE_IDENTIFIER } from "./contact-us/ContactUs";

describe("page identifiers", () => {
test("are not equal", () => {
expect(ACCESSIBILITY_PAGE_IDENTIFIER).not.toEqual(CONTACT_PAGE_IDENTIFIER);
});
});

0 comments on commit 6c0da5a

Please sign in to comment.