如何将模拟事件传递给 React.cloneElement() 方法返回的反应元素
How to pass mock event to the react element that React.cloneElement() method returned
index.ts
:
import React, { ChangeEvent, cloneElement, Component, isValidElement } from 'react';
interface MyComponentProps {
children?: React.ReactNode;
}
export default class MyComponent extends Component<MyComponentProps> {
render() {
const { children } = this.props;
const items = React.Children.map(children, (element, index) => {
if (!isValidElement(element)) {
return element;
}
return cloneElement(element, {
key: index,
onChange: (e: ChangeEvent<HTMLInputElement>) => {
element.props.onChange && element.props.onChange(e);
// do other things
console.log('handle change event');
},
});
});
return <div>{items}</div>;
}
}
index.test.ts
:
import { mount } from 'enzyme';
import React, { ChangeEvent } from 'react';
import MyComponent from '.';
class Test extends React.Component<{ onChange?: (e: ChangeEvent<HTMLInputElement>) => void }> {
render() {
return <input {...this.props} />;
}
}
describe('react-cloneElement-enzyme-change-event', () => {
it('should handle change event', () => {
const onChange = jest.fn();
const wrapper = mount(
<MyComponent>
<Test onChange={onChange} />
<Test />
</MyComponent>
);
const input = wrapper.find('div').children().at(0).find('input');
// expect(jest.isMockFunction(input.prop('onChange'))).toBeTruthy(); // failed
const event = {} as ChangeEvent<HTMLInputElement>;
input.simulate('change', event);
expect(onChange).toBeCalled();
expect(onChange).toBeCalledWith({}); // failed. The actual value is React synthetic event object
});
});
此断言 expect(onChange).toBeCalledWith({});
失败。我发现在执行 cloneElement
后,input
上的 onChange
事件处理程序已被替换为非模拟 onChange: () => {}
因此,即使在执行 input.simulate('change', event)
时传递了模拟事件对象,onChange
事件处理程序也会传递 React 合成事件对象,而不是模拟事件。
如何将模拟事件对象传递给克隆元素的 onChange
?
包版本:
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"jest": "^26.6.3",
"react": "^16.14.0",
最小的、可重现的示例存储库:https://github.com/mrdulin/jest-v26-codelab/tree/main/issues/react-cloneElement-enzyme-change-event
显然 enzyme-adapter-react-16
有两种不同的实现方式 simulateEvent 用于浅层和挂载,而对于挂载,它正在创建一个模拟事件,该事件与给定数据合并。
如果你通过了:
const event = {
target: {
value: 'z',
},
} as ChangeEvent<HTMLInputElement>;
input.simulate('change', event);
你可以期待:
expect(onChange).toHaveBeenCalledWith(expect.objectContaining(event));
因为 onChange
属性被匿名箭头函数覆盖。
我们可以使用 .invoke(invokePropName)(...args) => Any 调用一个函数 prop 并将我们模拟的事件对象传递给它。
例如
index.test.tsx
:
import { mount } from 'enzyme';
import React, { ChangeEvent } from 'react';
import MyComponent from '.';
class Test extends React.Component<{ onChange?: (e: ChangeEvent<HTMLInputElement>) => void }> {
render() {
return <input {...this.props} />;
}
}
describe('react-cloneElement-enzyme-change-event', () => {
it('should handle change event', () => {
const logSpy = jest.spyOn(console, 'log');
const onChange = jest.fn();
const wrapper = mount(
<MyComponent>
<Test onChange={onChange} />
<Test />
</MyComponent>
);
const input = wrapper.find('div').children().at(0).find('input');
expect(jest.isMockFunction(input.prop('onChange'))).toBeFalsy();
const event = {} as ChangeEvent<HTMLInputElement>;
input.invoke('onChange')!(event);
expect(onChange).toBeCalledWith({});
expect(logSpy).toBeCalledWith('handle change event');
});
});
单元测试结果:
PASS issues/react-cloneElement-enzyme-change-event/index.test.tsx
react-cloneElement-enzyme-change-event
✓ should handle change event (84 ms)
console.log
handle change event
at CustomConsole.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 90 | 75 | 100 | 90 |
index.tsx | 90 | 75 | 100 | 90 | 13
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.727 s
index.ts
:
import React, { ChangeEvent, cloneElement, Component, isValidElement } from 'react';
interface MyComponentProps {
children?: React.ReactNode;
}
export default class MyComponent extends Component<MyComponentProps> {
render() {
const { children } = this.props;
const items = React.Children.map(children, (element, index) => {
if (!isValidElement(element)) {
return element;
}
return cloneElement(element, {
key: index,
onChange: (e: ChangeEvent<HTMLInputElement>) => {
element.props.onChange && element.props.onChange(e);
// do other things
console.log('handle change event');
},
});
});
return <div>{items}</div>;
}
}
index.test.ts
:
import { mount } from 'enzyme';
import React, { ChangeEvent } from 'react';
import MyComponent from '.';
class Test extends React.Component<{ onChange?: (e: ChangeEvent<HTMLInputElement>) => void }> {
render() {
return <input {...this.props} />;
}
}
describe('react-cloneElement-enzyme-change-event', () => {
it('should handle change event', () => {
const onChange = jest.fn();
const wrapper = mount(
<MyComponent>
<Test onChange={onChange} />
<Test />
</MyComponent>
);
const input = wrapper.find('div').children().at(0).find('input');
// expect(jest.isMockFunction(input.prop('onChange'))).toBeTruthy(); // failed
const event = {} as ChangeEvent<HTMLInputElement>;
input.simulate('change', event);
expect(onChange).toBeCalled();
expect(onChange).toBeCalledWith({}); // failed. The actual value is React synthetic event object
});
});
此断言 expect(onChange).toBeCalledWith({});
失败。我发现在执行 cloneElement
后,input
上的 onChange
事件处理程序已被替换为非模拟 onChange: () => {}
因此,即使在执行 input.simulate('change', event)
时传递了模拟事件对象,onChange
事件处理程序也会传递 React 合成事件对象,而不是模拟事件。
如何将模拟事件对象传递给克隆元素的 onChange
?
包版本:
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"jest": "^26.6.3",
"react": "^16.14.0",
最小的、可重现的示例存储库:https://github.com/mrdulin/jest-v26-codelab/tree/main/issues/react-cloneElement-enzyme-change-event
显然 enzyme-adapter-react-16
有两种不同的实现方式 simulateEvent 用于浅层和挂载,而对于挂载,它正在创建一个模拟事件,该事件与给定数据合并。
如果你通过了:
const event = {
target: {
value: 'z',
},
} as ChangeEvent<HTMLInputElement>;
input.simulate('change', event);
你可以期待:
expect(onChange).toHaveBeenCalledWith(expect.objectContaining(event));
因为 onChange
属性被匿名箭头函数覆盖。
我们可以使用 .invoke(invokePropName)(...args) => Any 调用一个函数 prop 并将我们模拟的事件对象传递给它。
例如
index.test.tsx
:
import { mount } from 'enzyme';
import React, { ChangeEvent } from 'react';
import MyComponent from '.';
class Test extends React.Component<{ onChange?: (e: ChangeEvent<HTMLInputElement>) => void }> {
render() {
return <input {...this.props} />;
}
}
describe('react-cloneElement-enzyme-change-event', () => {
it('should handle change event', () => {
const logSpy = jest.spyOn(console, 'log');
const onChange = jest.fn();
const wrapper = mount(
<MyComponent>
<Test onChange={onChange} />
<Test />
</MyComponent>
);
const input = wrapper.find('div').children().at(0).find('input');
expect(jest.isMockFunction(input.prop('onChange'))).toBeFalsy();
const event = {} as ChangeEvent<HTMLInputElement>;
input.invoke('onChange')!(event);
expect(onChange).toBeCalledWith({});
expect(logSpy).toBeCalledWith('handle change event');
});
});
单元测试结果:
PASS issues/react-cloneElement-enzyme-change-event/index.test.tsx
react-cloneElement-enzyme-change-event
✓ should handle change event (84 ms)
console.log
handle change event
at CustomConsole.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 90 | 75 | 100 | 90 |
index.tsx | 90 | 75 | 100 | 90 | 13
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.727 s