I am writing a test to satisfy the following business rule:
If we select
Canada
from the country dropdown, show error messages on blur for the province and postal code fields if they are blank.
My React Final Form under test is:
<Form
onSubmit={onSubmit}
validate={values => {
const errors: ValidationErrors = {};
if (!values.country) {
errors.country = "Country must be selected";
}
if (values.country === "Canada" && !values.province) {
errors.province = "Province must be provided";
}
if (values.country === "Canada" && !values.postalCode) {
errors.postalCode = "Postal code must be provided";
}
return errors;
}}
render={({
...
}) => (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="country">Country</label>
<br />
<Field<string> id="country" name="country" component="select">
<option />
<option value="Canada">Canada</option>
<option value="US">US</option>
</Field>
</div>
<Condition when="country" is="Canada">
<div>
<label htmlFor="province">Province</label>
<br />
<Field id="province" name="province" component="input" />
<Error name="province" />
</div>
<div>
<label htmlFor="postalCode">Postal Code</label>
<br />
<Field id="postalCode" name="postalCode" component="input" />
<Error name="postalCode" />
</div>
</Condition>
...
</form>
)}
The test I am writing looks like this:
describe("Contact form", () => {
test("should show errors if Canada is seledted but province and postal code are blank", async () => {
const { getByLabelText, findByRole } = render(<ContactForm />);
fireEvent.change(getByLabelText("Country"), {
target: { value: "Canada" }
});
fireEvent.change(getByLabelText("Province"), {
target: { value: "" }
});
fireEvent.change(getByLabelText("Postal Code"), {
target: { value: "" }
});
fireEvent.blur(getByLabelText("Postal Code"));
const postalAlert = await findByRole("alert", {
name: "Postal code must be provided"
});
expect(postalAlert).toBeInTheDocument();
const provinceAlert = await findByRole("alert", {
name: "Province must be provided"
});
expect(provinceAlert).toBeInTheDocument();
I am attempting to trigger the error messages - which get rendered out with role="alert"
- but my test fails with: Unable to find role="alert"
Now, if I remove the name
property I am attempting to filter on, then the alert
is found:
const postalAlert = await findByRole("alert"); // SUCCESS
I could retrieve the alerts with findAllByRole
and iterate over them, but I'd really just like to query for each one explicitly with their accessible name and assert they are in the document.
I see the element when the screen is debugged, I just want to figure out how to query for it directly with its role and name:
<span
role="alert"
style="color: rgb(153, 0, 0);"
>
Postal code must be provided
</span>
Sample form: https://codesandbox.io/s/react-ts-unit-test-example-6scjc?file=/src/ContactForm.test.tsx
There are some reasons that you can not pass the test
Seem like span doesn't apply text content to be the accessible name, you can use aria-label to set the accessible name for it, more information here
export const Error = (props: Props) => {
const {
meta: { touched, error }
} = useField(props.name, { subscription: { touched: true, error: true } });
return touched && error ? (
// add aria-label to set accessiable name for span
<span aria-label={error} role="alert" style={{ color: "#900" }}>
{error}
</span>
) : null;
};
You only call blur "Postal Code", but your test include Province error too, the implementation is that you show Province error when you blur Province input, so you need to add that to your test
import * as React from "react";
import { render, fireEvent } from "@testing-library/react";
import { ContactForm } from "./ContactForm";
describe("Contact form", () => {
test("should show errors if Canada is seledted but province and postal code are blank", async () => {
const { getByLabelText, findByRole } = render(<ContactForm />);
fireEvent.change(getByLabelText("Country"), {
target: { value: "Canada" }
});
fireEvent.change(getByLabelText("Province"), {
target: { value: "" }
});
fireEvent.change(getByLabelText("Postal Code"), {
target: { value: "" }
});
// you only blur "Postal Code"
fireEvent.blur(getByLabelText("Postal Code"));
const postalAlert = await findByRole("alert", {
name: "Postal code must be provided"
});
expect(postalAlert).toBeInTheDocument();
// This will not shown because you have not called blur Province
// comment this or add the line below to pass the test
// fireEvent.blur(getByLabelText("Province"));
const provinceAlert = await findByRole("alert", {
name: "Province must be provided"
});
expect(provinceAlert).toBeInTheDocument();
});
});