【问题标题】:React-Bootstrap Invalid Form Feedback is always visible how to test?React-Bootstrap Invalid Form Feedback总是可见如何测试?
【发布时间】:2021-09-01 20:50:23
【问题描述】:

我无法使用 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

【问题讨论】:

  • 您是否考虑过使用testing-library.com/docs/ecosystem-user-event模拟实际的有效/无效输入,而不是直接触发事件?
  • 是的,我有,你可以在代码沙箱中看到。不幸的是,结果相同。我什至确保在断言反馈可见性之前输入实际上改变了值
  • 这可能是正确的,但我似乎无法理解可以做什么。我觉得这与我的jest.config.jsmoduleNameMapper 中的.css.scss 文件的存根有关 ????我将我的存储库添加到描述中,以防它提供进一步的见解。

标签: forms validation jestjs react-bootstrap react-testing-library


【解决方案1】:

因此,您似乎遇到了这个问题,因为使用 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 做到了这一点(请参阅上面的答案)。修复也可以使用带有引导程序的样式组件吗?我认为问题在于无法将引导程序“映射”到测试环境中的底层 css。但是,style-components 似乎考虑了风格 - 只是不确定它如何与引导程序一起工作。
  • 另外,您将如何将您的解决方案应用于多输入表单。显然,我的 MWE 只包含一个输入,但在应用程序中,一个输入会有很多。在这种情况下,您需要使用自定义函数(或模式)检查输入的有效性,这正是 Formik/Yup 真正有用的地方。
【解决方案2】:

我最终使用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 而不是 formik 的问题的解决方案。
  • 这确实使用了带有 Formik 的 react-bootstrap。我采用了一种以我认为合适的方式为我解决问题的方法。如果其他人面临同样的问题,他们可能会发现我的解决方案是一个很好的选择。话虽如此,您的方法(我在实施我的解决方案后看到的)也是可以接受的——尽管它可能无法很好地扩展到具有多个输入的表单。问题的核心在于,在测试期间实际上并没有读入样式信息,只有类名(除非您使用样式化的组件)。
猜你喜欢
  • 2021-07-06
  • 2023-02-09
  • 2018-03-04
  • 1970-01-01
  • 2019-10-30
  • 1970-01-01
  • 2021-02-27
  • 1970-01-01
  • 2017-08-26
相关资源
最近更新 更多