Open ghost opened 8 years ago
@darkjoker are you using shallow
or mount
? Can you share the code for the React component you're trying to test?
I've tried both after reading several posts, but i couldn't find a way to make the keydown, keypress or keyup events to work.
@darkjoker you can see a working example that I wrote this morning to verify the behavior: https://github.com/Aweary/enzyme-test-repo/blob/issue-441/test.js
class TestComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<input onKeyDown={this.props.onKeyDown} />
</div>
);
}
}
describe('Issue #441', () => {
it('It should simulate keydown events', () => {
const onKeyDown = sinon.spy();
const wrapper = mount(<TestComponent onKeyDown={onKeyDown}/>);
const input = wrapper.find('input');
input.simulate('keyDown', {keyCode: 40});
expect(onKeyDown.called).to.be.true;
});
This passes as I'd expect. If you can share a simplified reproducible case where it's not working that would be great. Feel free to fork that ^ repo to do so, if you'd like.
Thnaks for the quick response, the component code is in the attachement, the test code is as it follows:
describe("ComboBox Component", () => {
let options = [
{ "value": 0, "text": "United States" },
{ "value": 1, "text": "United Kingdom" },
{ "value": 16, "text": "Slovakia" }
];
it("it highlights option selected with keyboard on option on list", () => {
let combo = mount(
<ComboBox comboBoxElements={options}/>
);
// TestUtils.Simulate.keyDown(searchInput, { keyCode: 40 });
combo.find(".fa-chevron-down").simulate("click");
let input = combo.find("input");
input.simulate("keyDown", { keyCode: 40 });
expect(combo.find("li").at(1).prop("className")).toBe(("selectedOption selectedChildren"));
});
});
@darkjoker Did you resolve this? I'm seeing a similar problem.
No, I have been using TestUtils for this kind of tests
@darkjoker for mount
, the simulate
method is a thin wrapper around ReactTestUtils.Simulate
, so I feel like there's something else going on with your component that's causing it to fail. Can you share a simplified case reproducing the issue? The ComboBox
is rather large (and compiled) so it's hard to parse what's going on there.
I'll post a simplified version of the component asap only maintaining the core functionality I'm trying to test
+1, waiting for a solution.
@darkjoker @springuper try this, worked for me
wrapper.find('.myclass').simulate('keyDown', { key: 'ArrowLeft' });
For me the problem was I had added event listeners on the DOM elements themselves, and not in JSX (e.g. <div onKeyDown={} />
. I guess the simulate
method only works when events are registered on the components themselves, not the underlying DOM nodes.
You can refer to mapNativeEventNames
function in enzyme to know the correct key to use for each action: https://github.com/airbnb/enzyme/blob/master/src/Utils.js#L318. This just mocks the event object, so if you're using event.keyCode
in your code, just pass { keyCode: 40 }
as second parameter to simulate
.
It did not work for me until I used the which
property.
wrapper.find('.myclass').simulate('keyDown', { key: 'Tab', keyCode: 9, which: 9 });
I can remove both key
and keyCode
properties and it still works, but it's nice to see in the test what key it refers to.
Is it possible to simulate multiple keys, such as ctrls+s? Sorry for breaking into this discussion though :/
@tomitrescak that would require a keypress for s
with ctrlKey
set to true
, iirc.
I am having the same problem with the keyPress event. All other events can be simulated except key events on text input field
Having the same issue here. None of the solutions above worked for me.
@danyim @prijuly2000 if you use onChange to simulate input fields, only the last character in the string will be reflected in the keyDown event so you can use that to test keyPress
So you can slice the string input and check for each last character to simulate a simulation on keyPress via keyDown events!
import React, { Component } from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
export class Issue441 extends Component {
constructor() {
super();
this.state = {
change: '',
keyDown: ''
}
this.onChangeInput = this.onChangeInput.bind(this);
this.onKeyDownInput = this.onKeyDownInput.bind(this);
}
onChangeInput(e) {
this.setState({
change: e.target.value
})
}
onKeyDownInput(e) {
this.state({
keyDown: e.keyCode
});
// to check which of the following actually works on your system
if(e.keyCode)
console.log(e.keyCode)
if(e.key)
console.log(e.key)
if(e.which)
console.log(e.which)
}
render() {
return (
<div>
<input
class='myclass'
onChange={this.onChangeInput}
onKeyDown={this.onKeyDownInput}
/>
</div>
);
}
}
describe('<Issue441/>', () => {
const wrapper = mount(<Issue441/>);
it('should simulate change', () => {
wrapper.find('input.myclass').simulate('change', { target: { value: 'xyz' } });
expect(wrapper.state('change')).to.equal.('xyz');
expect(wrapper.state('keyDown')).to.equal.('z');
});
// theoretical testing of keyPress events
const st = 'string';
for(let i in st) {
it('should theoretical simulate keyPress', () => {
wrapper.find('input.myclass').simulate('change', { target: { value: st.slice(0, i += 1) } });
expect(wrapper.state('change')).to.equal.(st.slice(0, i += 1));
expect(wrapper.state('keyDown')).to.equal.(st[i]);
});
}
})
for(let i in st)
is the same as for(i=0; i < st.length; i++)
0, 1, 2, 3, 4, 5
for(let i of st)
will return i
as values of string s, t, r, i, n, g
It's counter intuitive that for testing a keyDown
event that you need to use change
. Personally, I believe that you should have the option to simulate all the events even if it's only an interface that will execute change
behind scenes.
@EduardoAC the example is to mock testing of keyPress
events and not keyDown
events
@aweary I managed to get it working on mount by using
.simulate('keyDown', { key: 'Enter', keyCode: 13, which: 13 })
However, the same example using shallow
rendering didn't work, Is any reason why shallow render doesn't allow to handle keyDown
?
@EduardoAC the example is to mock testing of keyPress events and not keyDown events
@pranjalk Honestly I don't know where to start to comment your post even if you example achieve the question asked. Personally, it's over engineer from my point of view because you are storing the event on the state
just for test that the event gets fire with the value expected.
Otherwise, you should target examples like "Brandon dail" propose, but replacing keyDown
for keyPress
.
jblok commented on Dec 8, 2016 For me the problem was I had added event listeners on the DOM elements themselves, and not in JSX (e.g.
<div onKeyDown={} />
. I guess the simulate method only works when events are registered on the components themselves, not the underlying DOM nodes.
I believe this is true. The simulate
function will only trigger the exact event handler that you specify. It is not doing a real simulation that causes events to get triggered but rather actually calling the specified event handler directly instead. For real simulation, you may need to use something like simulant.
Hello guys,
thanks for the enzyme!
here is some problem what I try to describe.
I got battleship game, as side project where I report bugs and try things. so this PR: https://github.com/eugene-matvejev/battleship-game-gui-react-js/pull/126/files when your component is mounted it add event listener on '<' and '>' arrows to switch pages backwards and forwards
if I remove it here: [as it doesn't work to be honest, as we need observer 'document level'] https://github.com/eugene-matvejev/battleship-game-gui-react-js/pull/125/files [but document 'level' binding is in Mount/Unmount]
test fails
do I'm doing something wrong? or how I could emulate 'document level' events? I presume because react's events are syntetic, it is impossible?
I was attempting to simulate a ArrowDown keydown event on an <input />
.
@pranjalk your solution worked for me with Enzyme 3. Thanks!
i found that if the events attached by element.addEventListener()
.simulate()
will not trigger the listener.
so my suggested solution is to simulate the event manually by element.dispatchEvent()
const simulateKeypress = (element, key) => {
let code = key.charCodeAt(0);
const event = new KeyboardEvent('keypress', {key: key, code, charCode: code, keyCode: code});
element.dispatchEvent(event);
};
Considering that you can trust the HTML event to raise the OnClick or OnChange even when requested, this is all unnecessary. Just call the OnChange prop of the React element.
I believe this is true. The simulate function will only trigger the exact event handler that you specify. It is not doing a real simulation that causes events to get triggered but rather actually calling the specified event handler directly instead. For real simulation, you may need to use something like simulant.
I sure wish you could trigger a click
event from the keyboard for accessibility testing using buttonComponent.simulate('keydown', {which: 13})
or similar, like real DOM nodes do. It's a great way to assert an HTML element is focusable and works from the keyboard, rather than testing only for mouse clicks. onClick
bindings never respond from key events in Enzyme with JSDOM, and binding to both keydown
and mousedown
just for testing purposes creates unnecessary complexity.
This is one of the hairiest accessibility problems I've come across in React testing. I've tried every relevant trick I could come up with, and I'm having to concede and write keyboard compatibility tests in Selenium Webdriver instead. Even Simulant doesn't seem to trigger event callbacks in this scenario, I suspect because of the way SyntheticEvent is delegating events through the DOM tree rather than on a specific button node.
@marcysutton I think our team is going to have to start using syntax like this to make it clear:
function handler(event) {
const evt = event.nativeEvent || event // works for React events
const key = evt.code || evt.which // prefers non deprecated api
...
}
It may be worth it to remove .which
all together to force older test scripts to get updated.
simulate
should be avoided. It does not faithfully simulate anything - it's just sugar for .prop('onClick')()
or similar.
When working with events created by addEventListener()
, it seems that simulate()
does not work properly ( using mount()
), e.g.
class AwInput extends Component {
inputElement = null;
componentDidMount() {
const node = this.inputElement;
// using some third-party library to manipulate the DOM node.
node.addEventListener("blur", innerEventHandler);
node.addEventListener("keydown", innerEventHandler);
}
render() {
const props = this.props;
return (
<inputWrapper
id={props.id}
value={props.value}
onChange={props.onChange}
innerRef={(el) => (this.inputElement = el)}
/>
);
}
}
function innerEventHandler (event) {
// simulate() never reaches this point
console.log('innerEventHandler:', event);
}
And writing the unit test for that component with Enzyme...
describe('Testing <AwInput />', () => {
const ctx = { value: 'testing' };
const onChange = jest.fn((e) => (ctx.value = e.target.value));
const Wrapper = mount(
<AwInput
id="awInput"
value={ctx.value}
onChange={onChange}
/>
);
const inputElement = Wrapper.find('#awInput').last();
it('Should render input element without any error', () => {
expect(inputElement.exists()).toBe(true); // success
expect(inputElement).toHaveLength(1); // success
});
it('Should call the mock onChange function', () => {
const value = 'new value';
inputElement.simulate('change', { target: { value } }); // success
expect(onChange).toHaveBeenCalled();
expect(ctx.value).toBe(value);
});
it('Should call the inner keyDown function', () => {
inputElement.simulate('keyDown', { key: 'a', keyCode: 97 }); // fail
// expected: console.log('innerEventHandler:', event);
});
});
So, as mentioned before, it seems the simulate()
method only works with events registered on the components themselves, but when dealing with events attached to the underlying DOM nodes, it does not work.
Using ReactTestUtils.Simulate is not working as expected neither :(
BTW, it's worth mentioning the Enzyme Future Work.
class TestComponent extends React.Component { constructor(props) { super(props); }
handleKDown = e => { if (e.key === 'Enter') { this.handleSubmit(); } };
render() { return (
);
} }
describe('Issue #441', () => { it('It should simulate keydown events', () => { const onKeyDown = sinon.spy(); const wrapper = mount(
); const input = wrapper.find('input'); input.simulate('keyDown', {keyCode: 40}); expect(onKeyDown.called).to.be.true; }); This passes as I'd expect. If you can share a simplified reproducible case where it's not working that would be great. Feel free to fork that ^ repo to do so, if you'd like.
Thank you what if I added an IF and handleKDown function (which calls submit) then the code will not be covered using jest, how do I make assertions for that please?
So far I got
const handleKDown = jest.fn();
......
.simulate('keydown', { 'keyDown', {keyCode: 13});
expect(handleKeyDown.mock.calls[0]).toBeCalled();
But it does not work, it kept saying 'undefined' or expected mockCall function ??? which is alienating to me
@codingarrow, I can see in your event-handler implementation you are using an argument, which is not the same provided in your test, let's see:
handleKDown = e => {
if (e.key === 'Enter') {
this.handleSubmit();
}
};
The e
parameter tries to access the .key
property which is not defined in your test. So, when simulating events, pass the arguments with the expected properties, or mocked properties, let's see:
// ...
const event = {
key: '@'
};
inputNode.simulate('keyDown', event);
expect(onKeyDown).toHaveBeenCalled();
The following example shows how to simulate the event.preventDefault()
method from a simulated event:
const onKeyDown = jest.fn((e) => {
if (e.key === '@') e.preventDefault();
});
const Wrapper = mount(
<AwInput
id="awInput"
value={ctx.value}
onKeyDown={onKeyDown}
/>
);
const inputNode = Wrapper.find('#awInput').last();
describe('Testing <AwInput />', () => {
it('Should call onKeyDown with key "@" and isDefaultPrevented should be true', () => {
let isDefaultPrevented = false;
const event = {
key: '@',
keyCode: '@'.charCodeAt(0), // 64
preventDefault: () => (isDefaultPrevented = true)
};
inputNode.simulate('keyDown', event);
expect(onKeyDown).toHaveBeenCalled();
expect(isDefaultPrevented).toBe(true);
});
});
Happy testing!!
Is it posible to simulate shift + enter?
@leon0707 the simulate
API doesn't actually simulate anything - what you can do, however, is manually invoke an onKeyDown
prop or similar, and pass a fake event object that has the right properties to mimic a shift+enter.
Problem
I'm truing to simulate keyboard events with enzyme but I couldn't find a single line of documentation or code example where keyboard events are implemented. I've tried using
and
I've also tried using other types of casing on the event name and key names but nothing worked.
While there's no error using any of the two examples I've mentioned the output just isn't the expected and using
TestUtils.Simulate.keyDown(searchInput, { keyCode: 40 });
all works as expected. Am I not using the correct syntax?