Enzyme: react-select simulate 'change' not working

Created on 18 May 2016  ·  16Comments  ·  Source: enzymejs/enzyme

How i can simulate 'change' event on react-select comonent?

wrapper.find('Select').simulate('change', { target: { value: 'text' }});
don't trigger change event

wrapper.props().onChange(event) working well

any ideas?

Most helpful comment

@idoo so with shallow I was able to get a passing test using react-select. You can see it here. enzyme-test-repo/tree/issue-#400. It seems that your find query is not accurately querying the node that the change event should be simulated on. If you just use shallow and query for the <Select/> component you can easily test that the onChange prop is being called.

ReactSelectTestComponent.js

import React from 'react';
import Select from 'react-select';


const options = [
  { value: 'one', label: 'One' },
  { value: 'two', label: 'Two' },
]

export default class ReactSelectTestComponent extends React.Component {


  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
  }

  onChange(event) {
    if (this.props.onChange) {
      this.props.onChange(event)
    }
  }

  render() {
    return (
      <div>
        <h1>Hello, world</h1>
        <Select
          name='test'
          value='one'
          options={options}
          onChange={this.onChange}
        />
      </div>
    )
  }
}

test.js

describe('react-select', () => {
  it('should call onChange with shallow', () => {
    const onChange = sinon.spy();
    const wrapper = shallow(<ReactSelectTestComponent onChange={onChange}/>);
    const selectWrapper = wrapper.find('Select');
    selectWrapper.simulate('change');
    expect(onChange.called).to.be.true;
  });
})

As for mount it seems likely that react-select isn't actually registering a change event on the input your querying, I'd have to look at the source more but it doesn't seem like they actually register an onChange handler with their DOM nodes.

If you look at the test files for react-select you can see they are using a specific node to test change events:

var typeSearchText = (text) => {
    TestUtils.Simulate.change(searchInputNode, { target: { value: text } });
};

Where searchInputNode seems to be defined as:

var searchInstance = ReactDOM.findDOMNode(instance.refs.input);
searchInputNode = null;
if (searchInstance) {
    searchInputNode = searchInstance.querySelector('input');
    if (searchInputNode) {
        TestUtils.Simulate.focus(searchInputNode);
    }

So you may want to follow that as an example of testing change events with mount, though shallow does test the onChange prop just fine.

All 16 comments

is this question about full DOM rendering or shallow rendering?

@nfcampos it's doesn't work for both cases

@idoo can you share your test case? If wrapper.props().onChange(event) works then it should work with shallow since all simulate does with a shallow wrapper is map the event name the event handler in props (so 'change' maps to props.onChange)

This is also potentially an issue with react-select. It might not actually be registering change events the way you expect it to.

@Aweary

  it('call callback when value is changed', () => {
    let callback = sinon.spy();
    let props = extend({ onValueChanged: callback }, BASIC_PROPS);
    const wrapper = mount(createElement(SelectInput, props));
    wrapper.find('.Select-input input').simulate('change');
    expect(callback.called).to.be.true; //—> false ˘o˘
  });

@idoo so with shallow I was able to get a passing test using react-select. You can see it here. enzyme-test-repo/tree/issue-#400. It seems that your find query is not accurately querying the node that the change event should be simulated on. If you just use shallow and query for the <Select/> component you can easily test that the onChange prop is being called.

ReactSelectTestComponent.js

import React from 'react';
import Select from 'react-select';


const options = [
  { value: 'one', label: 'One' },
  { value: 'two', label: 'Two' },
]

export default class ReactSelectTestComponent extends React.Component {


  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
  }

  onChange(event) {
    if (this.props.onChange) {
      this.props.onChange(event)
    }
  }

  render() {
    return (
      <div>
        <h1>Hello, world</h1>
        <Select
          name='test'
          value='one'
          options={options}
          onChange={this.onChange}
        />
      </div>
    )
  }
}

test.js

describe('react-select', () => {
  it('should call onChange with shallow', () => {
    const onChange = sinon.spy();
    const wrapper = shallow(<ReactSelectTestComponent onChange={onChange}/>);
    const selectWrapper = wrapper.find('Select');
    selectWrapper.simulate('change');
    expect(onChange.called).to.be.true;
  });
})

As for mount it seems likely that react-select isn't actually registering a change event on the input your querying, I'd have to look at the source more but it doesn't seem like they actually register an onChange handler with their DOM nodes.

If you look at the test files for react-select you can see they are using a specific node to test change events:

var typeSearchText = (text) => {
    TestUtils.Simulate.change(searchInputNode, { target: { value: text } });
};

Where searchInputNode seems to be defined as:

var searchInstance = ReactDOM.findDOMNode(instance.refs.input);
searchInputNode = null;
if (searchInstance) {
    searchInputNode = searchInstance.querySelector('input');
    if (searchInputNode) {
        TestUtils.Simulate.focus(searchInputNode);
    }

So you may want to follow that as an example of testing change events with mount, though shallow does test the onChange prop just fine.

@idoo I'm going to close this since there doesn't seem to be an issue with enzyme that is actionable, but feel free to keep discussion any issue your having here 👍

Had a similar issue trying to use Select.Async ..Thanks to Aweary's post I could achieve that in Enzyme

const wrapper = mount(< SomeComponentUsingSelectAsync onChange = {onChange} />); const select = wrapper.find(Select.Async); select.find('input').simulate('change', { target: {value:'some value'} }); ```

EDIT: rework for the Select.Async

Hi all, I put it to work with the following code (I hope I don't forget any portion of the code in the copy paste :-p ) :

import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';

import SearchableSelect from '../../../components/Form/SearchableSelect';

import Select from 'react-select';

describe('<SearchableSelect /> component', () => {
  const input = {
    value: undefined,
    onChange: sinon.spy(),
  };
  const options = [
    { label: 'Tic', value: 'Tac' },
    { label: 'Timon', value: 'Pumbaa' },
  ];
  const callback = sinon.spy();

  let wrapper;
  let select;

  beforeEach(() => {
    input.onChange.reset();
    callback.reset();
  });

  context('with a `static` fetcher', () => { // <Select />
    const props = { input, callback, options };

    before(() => {
      wrapper = mount(<SearchableSelect {...props} />);
      select = wrapper.find(Select);
    });

    it('should call input.onChange and the callback on Select changes', () => {
      const selectInput = select.find('input');
      selectInput.simulate('change', { target: { value: options[0].value } });
      selectInput.simulate('keyDown', { keyCode: 9, key: 'Tab' });
      sinon.assert.callOrder(props.input.onChange, props.callback);
    });
  });

  context('with an async fetcher', () => {
    const promise = Promise.resolve({ options: [options[1]] });
    const loadOptions = sinon.stub().withArgs(options[1].value).returns(promise);

    const props = {
      callback,
      input,
      loadOptions,
    };

    before(() => {
      wrapper = mount(<SearchableSelect {...props} />);
      select = wrapper.find(Select); // Select is a child component of Async
    });

    it('should call input.onChange and the callback on Select changes', () => {
      // https://github.com/JedWatson/react-select/blob/8387e607666cca6dbfbf5860e53188b319cc43d5/test/Async-test.js#L33
      // https://github.com/JedWatson/react-select/blob/8387e607666cca6dbfbf5860e53188b319cc43d5/test/Async-test.js#L129
      select.props().onInputChange(options[1].value); // simulate a search and launch loadOptions
      const inp = select.find('input'); // find the hidden input to simulate an option selection
      inp.simulate('change', { target: { value: options[1].value } });
      inp.simulate('keyDown', { keyCode: 9, key: 'Tab' }); // validate the selection
      sinon.assert.callOrder(props.input.onChange, props.callback);
    });
  });
});

The key was to add an keyDown simulation to validate the change event

The simulate method called on a select tag did not update the value of the select tag, but it did call the event handler function.

To make my Jest test pass, I programmatically simulated changing the value of the select item. This took a lot of console logging to figure out the correct sequence of keys to use. In my JSX, the select tag is given the attribute ref="my-select"

const mySelect = myMountedComponent.find(some_selector_pattern)
// Now change the value of select drop down index
myMountedComponent['node']['refs']['my-select']['selectedIndex'] = 2
mySelect.simulate('change')

I created this utility function to Enzyme test react-select using Typescript. It returns a string reference to the component you can use to make assertions.

const selectOption = (
  wrapper: ReactWrapper<{}, {}, React.Component<{}, {}, any>>,
  selector: string,
  option: string
): string => {
  wrapper.find(`${selector} input`).instance().value = option.charAt(0)
  wrapper.find(`${selector} input`).simulate('change', {
    target: { value: option.charAt(0) }
  })
  wrapper
    .find(`${selector} .react-select__menu div[children="${option}"]`)
    .simulate('click')
  return `${selector} .react-select__single-value`
}

Usage:

const select = selectOption(
        component,
        '#country',
        'United States of America'
      )

Assert, where component is added to the test using mount():

expect(component.find(select).text()).toEqual('United States of America')

I have had similar issues simulating changes with react-select, possibly due to using MaterialUI as well.

The solutions explained so far did not work for me unfortunately, but I finally came across a solution which may be useful to those using MaterialUI, react-select and enzyme:

const mockChangeCallBack = jest.fn();

const wrapper = mount(
      <ReactSelectTestComponent options={someOptions} onChange={mockChangeCallBack} />
    );

// Find input field on Select component (from the react-select module).
const input = wrapper.find(Select).find("input");

// Simulate the arrow down event to open the dropdown menu.
input.simulate("keyDown", { key: "ArrowDown", keyCode: 40 });

// Simulate the enter key to select the first option.
input.simulate("keyDown", { key: "Enter", keyCode: 13 });

// Assert that the onChange function has been called.
expect(mockOnChangeCB).toHaveBeenCalled();

/* 
 * Could further assert that the state has changed to the first 
 * option in the options list (e.g. someOptions[0]) etc...
 */

Thanks @scottbanyard that was helpful

What about if the react-select is given the prop searchable={false}? No input is rendered when this prop is given and it's just a plain dropdown. How do you simulate a change for that? I tried simulating a change on the Select element and it did not work.

@jamcreencia simulate doesn’t actually simulate anything, which is why i recommend avoiding it. It’s just sugar for invoking a prop function. If you want to invoke onChange, use .prop(‘onChange’)().

@ljharb simulate has a problem in fact. Sometimes it working but for one test simulate not trigger event change. Atfer many tests, with my team we are arrived to invoke onChange by using .invoke('onChange')() too.

I created this utility function to Enzyme test react-select using Typescript. It returns a string reference to the component you can use to make assertions.

const selectOption = (
  wrapper: ReactWrapper<{}, {}, React.Component<{}, {}, any>>,
  selector: string,
  option: string
): string => {
  wrapper.find(`${selector} input`).instance().value = option.charAt(0)
  wrapper.find(`${selector} input`).simulate('change', {
    target: { value: option.charAt(0) }
  })
  wrapper
    .find(`${selector} .react-select__menu div[children="${option}"]`)
    .simulate('click')
  return `${selector} .react-select__single-value`
}

Usage:

const select = selectOption(
        component,
        '#country',
        'United States of America'
      )

Assert, where component is added to the test using mount():

expect(component.find(select).text()).toEqual('United States of America')

this works for me for react-select 3.1.1 but only for Select not for Async Select

Was this page helpful?
0 / 5 - 0 ratings