Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
630 views
in Technique[技术] by (71.8m points)

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

I'm trying to unit test a very simple feature in a React app where I'm blocking the user from pasting into a textarea by adding an event.preventDefault() in the event handler, like so:

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

// ... pass it down as props

<TextareaComponent onPaste={handlePaste} />

The problem I'm having is that every method I've found of dispatching events in Jest or Enzyme just "simulates" the event by getting the function passed to the onPaste prop and calling it directly with a mock event object. That's not what I'm interested in testing.

Ideally I want to do something like this, testing that the actual value of the input hasn't changed after pasting:

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")

But haven't been able to find a method that works. Any help would be appreciated!

question from:https://stackoverflow.com/questions/65877764/how-to-fire-and-test-a-real-paste-event-not-simulated-by-calling-the-prop-in-j

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Since you're just testing against a synthetic event (and not some sort of secondary action -- like a pop up that warns the user that pasting is disabled), then the easiest and correct solution is to simulate a paste event, pass it a mocked preventDefault function, and then assert that the mocked function was called.

Attempting to make assertions against a real paste event is pointless as this a React/Javascript implementation (for example, making assertions that a callback function is called when an onPaste/onChange event is triggered). Instead, you'll want to test against what happens as a result of calling the callback function (in this example, making assertions that event.preventDefault was called -- if it wasn't called, then we know the callback function was never executed!).

Working example (click the Tests tab to run the assertions):

Edit Jest with Enzyme Testing (prevent paste)

To keep it simple, I'm just asserting that the input is initially empty and then only updates the value if an onChange event was triggered. This can very easily be adapted to have some sort of passed in prop influence the default input's 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("");
  });
});

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...