Search code examples
javascriptreactjsdom-eventscontenteditable

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?


Solution

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