如何在 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("");
});
});
我正在尝试对 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("");
});
});