React-Bootstrap Invalid Form Feedback is always visible 如何测试?
React-Bootstrap Invalid Form Feedback is always visible how to test?
我无法使用 React-bootstrap 测试表单的正确验证。
我想看到当输入模式无效时,一旦表单被验证就会显示无效的反馈文本。
带测试的工作代码和框:https://codesandbox.io/s/flamboyant-cerf-7t7jq
import React, { useState } from "react";
import { Form, Button, InputGroup } from "react-bootstrap";
export default function App(): JSX.Element {
const [validated, setValidated] = useState<boolean>(false);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setValidated(true);
};
return (
<Form
className="col-12 col-lg-5 trans-form"
noValidate
validated={validated}
onSubmit={handleSubmit}
>
<InputGroup className="my-2">
<InputGroup.Prepend>
<InputGroup.Text>Receiver Public Key</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
role="textbox"
className="text-truncate rounded-right"
type="text"
pattern="[A-Za-z0-9]{5}"
required
/>
<Form.Control.Feedback
className="font-weight-bold"
type="invalid"
role="alert"
>
Length or format are incorrect!
</Form.Control.Feedback>
</InputGroup>
<Button
role="button"
className="mt-2 font-weight-bold"
variant={"primary"}
type="submit"
block
>
Sign
</Button>
</Form>
);
}
测试
import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import App from "../src/App";
describe("form validation", () => {
test("invalid receiver public key length", async () => {
render(<App />);
userEvent.click(screen.getByRole("button"));
userEvent.type(screen.getByRole("textbox"), "invalid");
expect(screen.getByRole("textbox")).toHaveValue("invalid");
expect(
await screen.findByText("Length or format are incorrect!")
).toBeVisible();
});
// this test fails, making it seem like the invalid-feedback is always present
test("valid receiver public key length", async () => {
render(<App />);
userEvent.click(screen.getByRole("button"));
userEvent.type(screen.getByRole("textbox"), "valid");
expect(screen.getByRole("textbox")).toHaveValue("valid");
await waitFor(() => {
expect(
screen.queryByText("Length or format are incorrect!")
).not.toBeVisible(); // ← FAILS
});
});
});
结果
第二次测试失败
存储库
看来您遇到此问题是因为使用 SCSS 进行样式设置,而 React 测试库无法解释底层样式。
解决此问题的一种方法是在反馈组件上引入 属性(即添加 extra level of indirection)以记录验证结果:
import React, { useState } from "react";
import { Form, Button, InputGroup } from "react-bootstrap";
export default function App(): JSX.Element {
const [validated, setValidated] = useState<boolean>(false);
// Hook to store the result of the validation
const [validity, setValidity] = useState<boolean>(false);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = e.currentTarget;
// Persist the result of the validation
setValidity(form.checkValidity());
setValidated(true);
};
return (
<Form
className="col-12 col-lg-5 trans-form"
noValidate
validated={validated}
onSubmit={handleSubmit}
>
<InputGroup className="my-2">
<InputGroup.Prepend>
<InputGroup.Text>Receiver Public Key</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
role="textbox"
className="text-truncate rounded-right"
type="text"
pattern="[A-Za-z0-9]{5}"
required
/>
<Form.Control.Feedback
className="font-weight-bold"
type="invalid"
role="alert"
data-validity={validity}
>
Length or format are incorrect!
</Form.Control.Feedback>
</InputGroup>
<Button
role="button"
className="mt-2 font-weight-bold"
variant={"primary"}
type="submit"
block
>
Sign
</Button>
</Form>
);
}
一旦你有了这个,你就可以测试一个有效的验证结果,如下所示:
test("valid receiver public key length", async () => {
const { container } = render(<App />);
userEvent.type(screen.getByRole("textbox"), "valid");
userEvent.click(screen.getByRole("button"));
let validationFeedback;
await waitFor(() => {
validationFeedback = container.querySelector('[data-validity="true"]');
});
expect(validationFeedback).toBeTruthy();
});
我分叉了你的例子,并让它与上面的代码一起工作 here。
我最终使用 Formik 来获得相同(但更好)的功能,这也让我有条件地呈现错误消息:
已更新codesandbox
// App.js
import React from "react";
import * as yup from "yup";
import { Formik, ErrorMessage, Field } from "formik";
import { Form, Button, InputGroup } from "react-bootstrap";
export default function App(): JSX.Element {
return (
<Formik
validationSchema={yup.object().shape({
from: yup
.string()
.matches(/^[A-Za-z0-9]{5}$/, "invalid format")
.required("from field is required")
})}
onSubmit={async (data, { setSubmitting }) => {
setSubmitting(true);
alert("submitted: " + data.from);
setSubmitting(false);
}}
initialValues={{ from: "" }}
>
{({ handleSubmit, isSubmitting, touched, errors }) => (
<Form
className="col-12 col-lg-5 trans-form"
noValidate
onSubmit={handleSubmit}
>
<InputGroup className="my-2">
<InputGroup.Prepend>
<InputGroup.Text>Label</InputGroup.Text>
</InputGroup.Prepend>
<Field
as={Form.Control}
role="textbox"
aria-label="from input"
type="text"
name="from"
required
isInvalid={!!touched.from && !!errors.from}
isValid={!!touched.from && !errors.from}
/>
<ErrorMessage
name="from"
render={(errorMessage) => (
<Form.Control.Feedback
className="font-weight-bold"
type="invalid"
role="alert"
aria-label="from feedback"
>
{errorMessage}
</Form.Control.Feedback>
)}
/>
</InputGroup>
<Button
role="button"
className="mt-2 font-weight-bold"
variant={"primary"}
type="submit"
block
disabled={isSubmitting}
>
Sign
</Button>
</Form>
)}
</Formik>
);
}
// App.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import App from "../src/App";
describe("form validation", () => {
test("empty", async () => {
render(<App />);
const input = screen.getByRole("textbox", { name: /From Input/i });
input.focus();
input.blur(); // cause an error
expect(input).toHaveValue("");
const alert = await screen.findByRole("alert", { name: /From Feedback/i });
expect(alert).toBeInTheDocument();
expect(alert).toHaveTextContent("from field is required");
});
test("invalid length", async () => {
render(<App />);
const input = screen.getByRole("textbox", { name: /From Input/i });
const text = "aaaaaa";
userEvent.type(input, text);
input.blur(); // cause an error
expect(input).toHaveValue(text);
const alert = await screen.findByRole("alert", { name: /From Feedback/i });
expect(alert).toBeInTheDocument();
expect(alert).toHaveTextContent("invalid format");
});
test("valid length", async () => {
render(<App />);
const input = screen.getByRole("textbox", { name: /From Input/i });
const text = "bbbbb";
userEvent.type(input, text);
input.blur();
expect(input).toHaveValue(text);
expect(
screen.queryByRole("alert", { name: /From Feedback/i })
).not.toBeInTheDocument();
});
});
我无法使用 React-bootstrap 测试表单的正确验证。 我想看到当输入模式无效时,一旦表单被验证就会显示无效的反馈文本。
带测试的工作代码和框:https://codesandbox.io/s/flamboyant-cerf-7t7jq
import React, { useState } from "react";
import { Form, Button, InputGroup } from "react-bootstrap";
export default function App(): JSX.Element {
const [validated, setValidated] = useState<boolean>(false);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setValidated(true);
};
return (
<Form
className="col-12 col-lg-5 trans-form"
noValidate
validated={validated}
onSubmit={handleSubmit}
>
<InputGroup className="my-2">
<InputGroup.Prepend>
<InputGroup.Text>Receiver Public Key</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
role="textbox"
className="text-truncate rounded-right"
type="text"
pattern="[A-Za-z0-9]{5}"
required
/>
<Form.Control.Feedback
className="font-weight-bold"
type="invalid"
role="alert"
>
Length or format are incorrect!
</Form.Control.Feedback>
</InputGroup>
<Button
role="button"
className="mt-2 font-weight-bold"
variant={"primary"}
type="submit"
block
>
Sign
</Button>
</Form>
);
}
测试
import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import App from "../src/App";
describe("form validation", () => {
test("invalid receiver public key length", async () => {
render(<App />);
userEvent.click(screen.getByRole("button"));
userEvent.type(screen.getByRole("textbox"), "invalid");
expect(screen.getByRole("textbox")).toHaveValue("invalid");
expect(
await screen.findByText("Length or format are incorrect!")
).toBeVisible();
});
// this test fails, making it seem like the invalid-feedback is always present
test("valid receiver public key length", async () => {
render(<App />);
userEvent.click(screen.getByRole("button"));
userEvent.type(screen.getByRole("textbox"), "valid");
expect(screen.getByRole("textbox")).toHaveValue("valid");
await waitFor(() => {
expect(
screen.queryByText("Length or format are incorrect!")
).not.toBeVisible(); // ← FAILS
});
});
});
结果
第二次测试失败
存储库
看来您遇到此问题是因为使用 SCSS 进行样式设置,而 React 测试库无法解释底层样式。
解决此问题的一种方法是在反馈组件上引入 属性(即添加 extra level of indirection)以记录验证结果:
import React, { useState } from "react";
import { Form, Button, InputGroup } from "react-bootstrap";
export default function App(): JSX.Element {
const [validated, setValidated] = useState<boolean>(false);
// Hook to store the result of the validation
const [validity, setValidity] = useState<boolean>(false);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = e.currentTarget;
// Persist the result of the validation
setValidity(form.checkValidity());
setValidated(true);
};
return (
<Form
className="col-12 col-lg-5 trans-form"
noValidate
validated={validated}
onSubmit={handleSubmit}
>
<InputGroup className="my-2">
<InputGroup.Prepend>
<InputGroup.Text>Receiver Public Key</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
role="textbox"
className="text-truncate rounded-right"
type="text"
pattern="[A-Za-z0-9]{5}"
required
/>
<Form.Control.Feedback
className="font-weight-bold"
type="invalid"
role="alert"
data-validity={validity}
>
Length or format are incorrect!
</Form.Control.Feedback>
</InputGroup>
<Button
role="button"
className="mt-2 font-weight-bold"
variant={"primary"}
type="submit"
block
>
Sign
</Button>
</Form>
);
}
一旦你有了这个,你就可以测试一个有效的验证结果,如下所示:
test("valid receiver public key length", async () => {
const { container } = render(<App />);
userEvent.type(screen.getByRole("textbox"), "valid");
userEvent.click(screen.getByRole("button"));
let validationFeedback;
await waitFor(() => {
validationFeedback = container.querySelector('[data-validity="true"]');
});
expect(validationFeedback).toBeTruthy();
});
我分叉了你的例子,并让它与上面的代码一起工作 here。
我最终使用 Formik 来获得相同(但更好)的功能,这也让我有条件地呈现错误消息:
已更新codesandbox
// App.js
import React from "react";
import * as yup from "yup";
import { Formik, ErrorMessage, Field } from "formik";
import { Form, Button, InputGroup } from "react-bootstrap";
export default function App(): JSX.Element {
return (
<Formik
validationSchema={yup.object().shape({
from: yup
.string()
.matches(/^[A-Za-z0-9]{5}$/, "invalid format")
.required("from field is required")
})}
onSubmit={async (data, { setSubmitting }) => {
setSubmitting(true);
alert("submitted: " + data.from);
setSubmitting(false);
}}
initialValues={{ from: "" }}
>
{({ handleSubmit, isSubmitting, touched, errors }) => (
<Form
className="col-12 col-lg-5 trans-form"
noValidate
onSubmit={handleSubmit}
>
<InputGroup className="my-2">
<InputGroup.Prepend>
<InputGroup.Text>Label</InputGroup.Text>
</InputGroup.Prepend>
<Field
as={Form.Control}
role="textbox"
aria-label="from input"
type="text"
name="from"
required
isInvalid={!!touched.from && !!errors.from}
isValid={!!touched.from && !errors.from}
/>
<ErrorMessage
name="from"
render={(errorMessage) => (
<Form.Control.Feedback
className="font-weight-bold"
type="invalid"
role="alert"
aria-label="from feedback"
>
{errorMessage}
</Form.Control.Feedback>
)}
/>
</InputGroup>
<Button
role="button"
className="mt-2 font-weight-bold"
variant={"primary"}
type="submit"
block
disabled={isSubmitting}
>
Sign
</Button>
</Form>
)}
</Formik>
);
}
// App.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import App from "../src/App";
describe("form validation", () => {
test("empty", async () => {
render(<App />);
const input = screen.getByRole("textbox", { name: /From Input/i });
input.focus();
input.blur(); // cause an error
expect(input).toHaveValue("");
const alert = await screen.findByRole("alert", { name: /From Feedback/i });
expect(alert).toBeInTheDocument();
expect(alert).toHaveTextContent("from field is required");
});
test("invalid length", async () => {
render(<App />);
const input = screen.getByRole("textbox", { name: /From Input/i });
const text = "aaaaaa";
userEvent.type(input, text);
input.blur(); // cause an error
expect(input).toHaveValue(text);
const alert = await screen.findByRole("alert", { name: /From Feedback/i });
expect(alert).toBeInTheDocument();
expect(alert).toHaveTextContent("invalid format");
});
test("valid length", async () => {
render(<App />);
const input = screen.getByRole("textbox", { name: /From Input/i });
const text = "bbbbb";
userEvent.type(input, text);
input.blur();
expect(input).toHaveValue(text);
expect(
screen.queryByRole("alert", { name: /From Feedback/i })
).not.toBeInTheDocument();
});
});