Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to simulate keyboard events #441

Open
ghost opened this issue Jun 7, 2016 · 34 comments
Open

Unable to simulate keyboard events #441

ghost opened this issue Jun 7, 2016 · 34 comments

Comments

@ghost
Copy link

ghost commented Jun 7, 2016

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

wrapper.find(".myclass").simulate("keyDown", {
            target: {
                keyCode: 40,
                which: 40,
                key: "down arrow"
            }
        });

and

wrapper.find(".myclass").simulate("keyDown", {
                keyCode: 40,
                which: 40,
                key: "down arrow"

        });

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?

@aweary
Copy link
Collaborator

aweary commented Jun 7, 2016

@DarkJoker are you using shallow or mount? Can you share the code for the React component you're trying to test?

@ghost
Copy link
Author

ghost commented Jun 7, 2016

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.

@aweary
Copy link
Collaborator

aweary commented Jun 7, 2016

@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.

@ghost
Copy link
Author

ghost commented Jun 8, 2016

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

    });

});

ComboBox.zip

@hamishtaplin
Copy link

@DarkJoker Did you resolve this? I'm seeing a similar problem.

@ghost
Copy link
Author

ghost commented Sep 6, 2016

No, I have been using TestUtils for this kind of tests

@aweary
Copy link
Collaborator

aweary commented Sep 6, 2016

@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.

@ghost
Copy link
Author

ghost commented Sep 6, 2016

I'll post a simplified version of the component asap only maintaining the core functionality I'm trying to test

@springuper
Copy link

+1, waiting for a solution.

@pranjalk
Copy link

@DarkJoker @springuper try this, worked for me

wrapper.find('.myclass').simulate('keyDown', { key: 'ArrowLeft' });

@jblok
Copy link

jblok commented 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.

@abhishek1nair
Copy link

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.

@simeg
Copy link

simeg commented Feb 9, 2017

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.

@tomitrescak
Copy link

Is it possible to simulate multiple keys, such as ctrls+s? Sorry for breaking into this discussion though :/

@ljharb
Copy link
Member

ljharb commented Mar 6, 2017

@tomitrescak that would require a keypress for s with ctrlKey set to true, iirc.

@prijuly2000
Copy link

I am having the same problem with the keyPress event. All other events can be simulated except key events on text input field

@danyim
Copy link

danyim commented Jul 14, 2017

Having the same issue here. None of the solutions above worked for me.

@pranjalk
Copy link

pranjalk commented Jul 14, 2017

@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]);
    });
  }
})

NOTE

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

@EduardoAC
Copy link

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.

@pranjalk
Copy link

pranjalk commented Aug 4, 2017

@EduardoAC the example is to mock testing of keyPress events and not keyDown events

@EduardoAC
Copy link

EduardoAC commented Aug 4, 2017

@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.

@techrah
Copy link

techrah commented Sep 1, 2017

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.

eugene-matvejev pushed a commit to eugene-matvejev/react-battleship that referenced this issue Sep 15, 2017
@eugene-matvejev
Copy link

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?

@hzhu
Copy link

hzhu commented Feb 15, 2018

I was attempting to simulate a ArrowDown keydown event on an <input />.

@pranjalk your solution worked for me with Enzyme 3. Thanks!

@elhay-av
Copy link

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);
};

@tomitrescak
Copy link

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.

@marcysutton
Copy link

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.

@AutoSponge
Copy link

AutoSponge commented Jun 22, 2018

@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.

@ljharb
Copy link
Member

ljharb commented Jul 4, 2018

simulate should be avoided. It does not faithfully simulate anything - it's just sugar for .prop('onClick')() or similar.

@jherax
Copy link

jherax commented Nov 26, 2018

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.

@codingarrow

This comment has been minimized.

@jherax

This comment has been minimized.

@leon0707
Copy link

Is it posible to simulate shift + enter?

@ljharb
Copy link
Member

ljharb commented Jan 21, 2020

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests