如何在 Jest 和 Enzyme 中触发和测试真实的粘贴事件(不是通过调用 prop 模拟的)

How to fire and test a real paste event (not simulated by calling the prop) in Jest and Enzyme

我正在尝试对 React 应用程序中的一个非常简单的功能进行单元测试,我通过在事件处理程序中添加 event.preventDefault() 来阻止用户粘贴到文本区域,如下所示:

function handlePaste(event) {
    event.preventDefault();
}

// ... pass it down as props

<TextareaComponent onPaste={handlePaste} />

我遇到的问题是,我在 Jest 或 Enzyme 中发现的每一种调度事件的方法都只是通过将函数传递给 onPaste 道具并直接调用它来“模拟”事件模拟事件对象。那不是我感兴趣的测试。

理想情况下我想做这样的事情,测试粘贴后输入的实际值没有改变:

const wrapper = mount(<ParentComponent inputValue="Prefilled text" />);

const input = wrapper.find(TextareaComponent);

expect(input.value).toEqual("Prefilled text")

input.doAPaste("Pasted text")

expect(input.value).not.toEqual("Pasted text")
expect(input.value).toEqual("Prefilled text")

但一直未能找到有效的方法。如有任何帮助,我们将不胜感激!

由于您只是针对合成事件进行测试(而不是某种次要操作——比如警告用户粘贴被禁用的弹出窗口),那么最简单和正确的解决方案是 simulate 粘贴事件,将模拟的 preventDefault 函数传递给它,然后断言调用了模拟函数。

尝试针对真正的粘贴事件做出断言是毫无意义的,因为这是一个 React/Javascript 实现(例如,断言在 onPaste/onChange 时调用回调函数事件被触发)。相反,您需要测试调用回调函数后发生的情况(在此示例中,断言 event.preventDefault 已被调用——如果未调用,则我们知道回调函数已被调用从未执行过!)。

工作示例(单击 Tests 选项卡以 运行 断言):

为简单起见,我只是断言输入最初为空,然后仅在 onChange 事件被触发时才更新值。这可以很容易地进行调整,使某种传入的道具影响默认输入的 value.


App.js

import React, { useCallback, useState } from "react";

const App = () => {
  const [value, setValue] = useState("");

  const handleChange = useCallback(
    ({ target: { value } }) => setValue(value),
    []
  );

  const handlePaste = useCallback((e) => {
    e.preventDefault();
  }, []);

  const resetValue = useCallback(() => {
    setValue("");
  }, []);

  const handleSubmit = useCallback(
    (e) => {
      e.preventDefault();
      console.log(`Submitted value: ${value}`);
      setValue("");
    },
    [value]
  );

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="foo">
        <input
          id="foo"
          type="text"
          data-testid="test-input"
          value={value}
          onPaste={handlePaste}
          onChange={handleChange}
        />
      </label>
      <br />
      <button data-testid="reset-button" type="button" onClick={resetValue}>
        Reset
      </button>
      <button type="submit">Submit</button>
    </form>
  );
};

export default App;

App.test.js

import React from "react";
import { configure, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App from "./App";

configure({ adapter: new Adapter() });

const value = "Hello";

describe("App", () => {
  let wrapper;
  let inputNode;
  beforeEach(() => {
    wrapper = mount(<App />);
    // finding the input node by a 'data-testid'; this is not required, but easier 
    // when working with multiple form elements and can be easily removed 
    // when the app is compiled for production
    inputNode = () => wrapper.find("[data-testid='test-input']");
  });

  it("initially displays an empty input", () => {
    expect(inputNode()).toHaveLength(1);
    expect(inputNode().props().value).toEqual("");
  });

  it("updates the input's value", () => {
    inputNode().simulate("change", { target: { value } });

    expect(inputNode().props().value).toEqual(value);
  });

  it("prevents the input's value from updating from a paste event", () => {
    const mockPreventDefault = jest.fn();
    const prefilledText = "Goodbye";

    // updating input with prefilled text
    inputNode().simulate("change", { target: { value: prefilledText } });

    // simulating a paste event with a mocked preventDefault
    // the target.value isn't required, but included for illustration purposes
    inputNode().simulate("paste", {
      preventDefault: mockPreventDefault,
      target: { value }
    });

    // asserting that "event.preventDefault" was called
    expect(mockPreventDefault).toHaveBeenCalled();

    // asserting that the input's value wasn't changed
    expect(inputNode().props().value).toEqual(prefilledText);
  });

  it("resets the input's value", () => {
    inputNode().simulate("change", { target: { value } });
    wrapper.find("[data-testid='reset-button']").simulate("click");

    expect(inputNode().props().value).toEqual("");
  });

  it("submits the input's value", () => {
    inputNode().simulate("change", { target: { value } });
    wrapper.find("form").simulate("submit");

    expect(inputNode().props().value).toEqual("");
  });
});