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
    });
  });
});

结果

第二次测试失败

存储库

https://github.com/lbragile/LibraCoin/tree/develop

看来您遇到此问题是因为使用 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();
  });
});