David Tuite
David Tuite

Reputation: 22663

Triggering onInput handler

Imagine a simple contentEditable component with an oninput event listener.

export default React.createClass({
  render() {
    return React.createElement('p', {
      contentEditable: true,
      onInput: this.emitChange
    });
  },

  emitChange(e) {
    console.log("Input event occurred", e);
  }
});

In my test, I simulate an input event on the component (like the user typing the letter a in the contentEditable tag).

This works in the browser, I can click the component, press the a key and I will see an 'a' in the tag and the console.log will trigger. I just can't get it to work in this test:

// Require the code from the block above.
var CE = require('content-editable');

describe('Component', function() {
  it('updates state when we type', function() {
    // Render a component instance and get a handle on it.
    let ce = TestUtils.renderIntoDocument(<CE/>);
    let _ce = ReactDOM.findDOMNode(ce);

    TestUtils.Simulate.input(_ce, {
      key: 'a'
    });

    expect(_ce.textContent).to.equal('a');
  });
});

This test fails because _ce.textContent is an empty string. I've tried simulating a click on the _ce before simulating the input and it doesn't fix the problem.

How can I get my test to pass?

Upvotes: 4

Views: 1422

Answers (1)

user4872511
user4872511

Reputation:

The main issue is that TestUtils.Simulate does not actually send an event to the DOM, it creates a fake event and sends it through React's event handling system. You can mitigate this by propagating the change event back up to the parent component via a callback, which you'll likely need to do anyway to retrieve the value of the editable:

export default React.createClass({
  propTypes: {
    onChange: React.PropTypes.func
  },

  render() {
    return React.createElement('p', {
      contentEditable: true,
      onInput: this.emitChange
    });
  },

  emitChange(e) {
    if (this.props.onChange) {
      this.props.onChange(e);
    }
  }
});

Now we can test that onChange was called:

describe('Component', function() {
  it('updates state when we type', function() {
    let changeFired = false;
    let ce = TestUtils.renderIntoDocument(<CE onChange={verify} />);
    let _ce = ReactDOM.findDOMNode(ce);

    TestUtils.Simulate.input(_ce, {
      key: 'a'
    });

    function verify(e) {
      changeFired = true;
      expect(e.key).to.equal('a');
    }

    expect(changeFired).to.equal(true);
  });
});

This will unit test the code in your emitChange function without any actual interaction with the rest of the environment. This is probably what you want in a unit test because your component doesn't care what the browser does to the DOM when the user types a key, all it cares about is that it is able to do something when the browser tells it that something has happened.

Generally in a React unit test, you won't see the textContent and innerHTML DOM properties change without causing the component to re-render. If you need to test browser interactions more thoroughly, you may need to pursue an integration (end-to-end) testing solution.

Upvotes: 2

Related Questions