enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

Testing select changes? #389

Closed dijs closed 8 years ago

dijs commented 8 years ago

Does anyone have an easy way to simulate and test select element changes?

This is what I am doing, and it is not working at all...

By the way the "option" elements that the component renders in the browser are not able to be selected using enzyme for some reason.

document.body.innerHTML = `
      <select>
        <option value="hello">Hello</option>
        <option value="goodbye" selected>Goodbye</option>
      </select>
    `;
    const props = {
      node: document.querySelector('select'),
    };
    const wrapper = mount(<Component {...props} />);
    expect(wrapper.render().find('select [selected]').val()).to.equal('goodbye');
    wrapper.find('select').node.value = 'hello';
    wrapper.find('select').simulate('change');
    expect(wrapper.render().find('select [selected]').val()).to.equal('hello');
  });
sattaman commented 8 years ago

We have just been facing a similar issue.

We found an existing issue: https://github.com/airbnb/enzyme/issues/218

It seems the correct way would be wrapper.find('select').simulate('change', {target { value : 'hello'}});

dijs commented 8 years ago

I tried that line using mount, I got the same result. No luck...

dijs commented 8 years ago

You would think this should work also. But it doesn't...

    const wrapper = mount(<Component {...props} />);
    expect(wrapper.find('[selected=true]').node.value).to.equal('goodbye');
    wrapper.find('select').find('option').at(0).simulate('click');
    expect(wrapper.find('[selected=true]').node.value).to.equal('hello');
dijs commented 8 years ago

I tried regular TestUtils also. Still no luck...

    const form = TestUtils.renderIntoDocument(<Form {...props} />);
    const select = TestUtils.findRenderedDOMComponentWithTag(form, 'select');
    const node = ReactDOM.findDOMNode(select);
    const options = TestUtils.findAllInRenderedTree(addressLocationForm, comp => {
      return comp instanceof HTMLOptionElement;
    });
    expect(node.value).to.equal('goodbye');
    TestUtils.Simulate.click(options[0]);
    expect(node.value).to.equal('hello');
dijs commented 8 years ago

Turn's out selected is only for "initial" values on options.

Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option

So that was my issue, I was depending on selected to always be on the "selected" option.

schovi commented 8 years ago

@dijs Today we solving same issue and found it can be resolved with using render instead of mount

    const wrapper = render(<StartChatForm {...props} />);
    expect(wrapper.find('option')).to.have.length(2);
    expect(wrapper.find('select [selected]').val()).to.equal('key');
danielstern commented 8 years ago

I am still unable to test select changes

knuterik91 commented 8 years ago

I solved this issue like this. Big thanks to @sattaman

it("when simulating a change, select should update its value", () => { const wrapper = setupMount(); wrapper.find('select').simulate('change',{target: { value : '100'}}); expect(wrapper.find('select').props().value).toBe("100"); });

fernandopasik commented 7 years ago

That didn't work for me. @knuterik91 Can you share what the setupMount function does?

knuterik91 commented 7 years ago

@fernandopasik hmm setupMount is a just a function that calls mount on my react component.

function setupMount(props = initProps) { return mount(<myReactComponent{...props}/>); }

what type of errors are you getting ?

fernandopasik commented 7 years ago

Thanks, I wasn't using mount. Now it works

NealEhardt commented 7 years ago

I have a component that keeps a ref to the <select>and reads its selectedIndex on a later event.

class Prompt extends Component {
  handleSubmit = () => {
    const { value } = this.dropdown.options[this.dropdown.selectedIndex]; // ::READ::
    this.props.onSubmit(value);
  };
  render () {
    return (
      <form onSubmit={ this.handleSubmit }>
        <select ref={ r => this.dropdown = r } defaultValue='b'>
          <option value='a'>A</option>
          <option value='b'>B</option>
          <option value='c'>C</option>
        </select>
        <input type='submit' value='Submit' />
      </form>
    );
  }
}

I was able to test it by mutating the select's underlying node.

it('should select C and submit it', () => {
  const onSubmit = sinon.stub();
  const wrapper = enzyme.mount(<Prompt onSubmit={ onSubmit } />);

  wrapper.find('select').node.selectedIndex = 2; // ::MUTATE::
  wrapper.find('form').simulate('submit');

  expect(onSubmit).to.have.been.calledWithExactly('c');
});
ljharb commented 7 years ago

@NealEhardt an unrelated note; you want to do this.handleSubmit = this.handleSubmit.bind(this) in the constructor and onSubmit={this.handleSubmit} in render rather than doing the bind in the render path; it's much more performant.

NealEhardt commented 7 years ago

@ljharb You're right. I was going for brevity but we don't want to encourage bad practices. Updated to the new instance-variable hottness.

ljharb commented 7 years ago

@NealEhardt no worries :-) altho using class variables like that is slightly less performant than using a prototype method that's merely bound per-instance (as opposed to being defined per-instance)

sivakumarbankuru commented 7 years ago

[[hi guys i am facing issue while writing test cases for selectfield in material-ui.... Can anyone fix it!

 <SelectField id='sex' name='sex' floatingLabelText='Sex*' onChange={(evt, newIndex, value) => this.sexDidChange(evt, newIndex, value)} value={this.state.sex} errorText={this.state.errors.sex} >
                      {
                        genders.map((w, index) => <MenuItem key={index.id} label={w.name} value={w.id}>{w.name}</MenuItem>)
                      }
                    </SelectField>

I want to match items presnt in menuitem..

it('finding the items present in SelectField of sex', () => {
    const wrapper = mount(<ReduxForm />,
      {
        context: {
          muiTheme: getMuiTheme(),
        },
        childContextTypes: {
          muiTheme: React.PropTypes.object.isRequired,
        },
      });
    expect(wrapper.find('MenuItem').first().text()).to.be.equal('Male');
  });
aakarim commented 7 years ago
const component = shallow(
      <ComponentWithCustomSelectComponentAsChild
        id="1"
        name="test"
      />,
    );
component.find(ImportedCustomSelectComponent).simulate('select', 'test');      
expect(component.state('selectHandlerValueThatChangesStateValue')).toEqual('test');

works for me

suwi commented 6 years ago

Why this issue is closed? Because someone write something what "works for him" and its not confirmed by anyone else?

ljharb commented 6 years ago

@suwi if the OP of the issue has solved it, it's solved. Please open a new issue if you're still having problems.

unfalse commented 6 years ago

None of the solutions presented here is working for me.

betterkenly commented 6 years ago
describe('to test <SelectMenu /> functionality', () => {
  const mockFunc = jest.fn();
  const options = ['a', 'b', 'c', 'd']
  const wrapper = mount(<SelectMenu label="the-label"
                                    options={options}
                                    name="the-name"
                                    action={mockFunc} />)

  it('should call action prop with correct input value as arguments when the input is changed', () => {
    wrapper.find('select').simulate('change', { target: { value: 'a' } });
    expect(mockFunc.mock.calls[0][0].value).toEqual('a');
  })
})

This is how i do it.

cgungaloo commented 6 years ago

Hi Ive been struggling with this and cant seem to get it to work.

it('should select transmission',() =>{

  const wrapper = mount(<Transmission/>);
  const select = wrapper.find('select').first();
  select.simulate('change',{target: {value:"manual"}});
  expect(select.props().value).toEqual('manual');
});

const Transmission=(props) =>{ return(

<select name="transmission" onChange={props.handleChange}>
  <option value="automatic">Automatic</option>
  <option value="manual">Manual</option>
</select>
);

} export default Transmission;

Even if I remove the onChange method from the select, the error is the same:

Expected value to equal:
  "manual"
Received:
  undefined

Difference:

  Comparing two different types of values. Expected string but received undefined.

Appreciate some help.

NealEhardt commented 6 years ago

If you want your test to behave as a user interaction would, your test needs to set the value, then simulate the change event. Enzyme doesn't set the value when you simulate a change.

It's a bit surprising that Enzyme works this way, but I think it allows Enzyme's implementation to be simple and reliable.

berlin-ab commented 6 years ago

We were able to observe a change event on a select box by simulating a change event on the option itself.

wrapper.find('select option[value="my-value"]').simulate('change')

mxkxf commented 6 years ago

I've tried all the examples in this issue and in the #218 issue too, no dice.

Here's my code:

const App = () => (
  <select>
    <option value="one">One</option>
    <option value="two">Two</option>
    <option value="three">Three</option>
  </select>
);

const wrapper = mount(<App />);
wrapper.find("select").simulate("change", { target: { value: "two" } });

expect(wrapper.find("select").props().value).toBe("two");

The result is always undefined. Have also tried shallow(<App />) too.

wsmd commented 6 years ago

Here's how I ended up testing select elements in my app:

I have a component that wraps a <select> element:

// code is simplified for clarity

class MyComponent extends React.Component {
  render() {
    return (
      <div>
        <select onChange={this.handleChange} value={this.props.value}>
          {this.renderOptions()}
        </select>
      </div>
    );
  }
}

This component depends not only the value of the select element, but also on its options (HTMLSelectElement.options) and its selectedIndex (HTMLSelectElement.selectedIndex) for various reasons.

handleChange = e => {
  const { value, selectedIndex, options } = e.target;
}

So while the some of solutions provided in the comments above work fine for simpler scenarios:

wrapper.find('select').simulate('change', { target: { value: 'some_value' } });

That did not work for me since I had to provide options (as HTMLOptionsCollection... ouch!) and selectedIndex to the mock event object as the second argument of simulate.

The trick that did it for me was to access and manipulate the DOM node of the select element and pass it back to .simulate, as the target of the mock event object, to let it do its job.

I'm not sure how reliable this solution is, but I wanted to share it here :)

const handleOnChange = jest.fn();
const wrapper = mount(
  <MyComponent onChange={handleOnChange} {...mockProps} />
);

const select = wrapper.find('select');
const selectDOMNode = select.getDOMNode(); // or .instance()

selectDOMNode.value = 'some_value';

// passing back the entire DOM node as the mock event target
select.simulate('change', { target: selectDOMNode });

expect(handleOnChange).toHaveBeenCalledWith(/* whatever MyComponent.props.onChange is supposed to receive */);
mrampiah commented 6 years ago

Fully understanding that refs are to be avoided whenever possible, this is the only solution I found to my problem.

//element constructor
constructor(props) {
    super(props)
    this.state = {user: {role: ''}}
    this.roleRef = React.createRef()
}

//in render method:
<select id="role" value={this.state.user.role} onChange={this.handleChange} ref={this.roleRef}>
//...
</select>

//in test
it('should change value to match user role', done => {
    var role = mount(<Form />).find('#role')

    const roles = require('../assets/roles.json')
    roles.forEach(element => {
        role.props().onChange({ target: { id: 'role', value: element.title } })
        expect(role.get(0).ref.current.value).toBe(element.title)
    }
    done()
}

UPDATE: I managed to get rid of refs by manually updating the wrapper.

roles.forEach(element => {
    role.props().onChange({ target: { id: 'role', value: element.title } })

    //update and reassign variables
    wrapper = wrapper.update()
    role = wrapper.find('#role')

    expect(role.props().value).toBe(element.title)
}
mcshakes commented 5 years ago

ditto @mikefrancis. Trying to simulate a simple select action on a simple dropdown is proving impossible.

    const wrapper = mount(<PredicateSelection/>);

    // wrapper.find("select").simulate("change",
    //   {target: {value : "user_email"} }
    // )

    wrapper.find("select option[value='user_email']").simulate("change")

  })

Above, the option that is commented out does what everybody else suggests. The second follows a pattern where we make the selected value. Both throw this error:

TypeError: Cannot read property '0' of undefined

It is strange

ljharb commented 5 years ago

Don't use simulate. If you want to invoke the onChange prop, do .prop('onChange')().

citypaul commented 5 years ago

@ljharb forcing an explicit .prop() invocation creates a tight coupling between the test and the implementation. Much nicer would be to be able to simulate clicking on the element in the DOM that results in an internal state change, which could then be verified by seeing the resulting update in the DOM. This way the tests simulate more accurately what a user would do, and it reduces the coupling between the test and the implementation, which makes refactoring easier over time.

ljharb commented 5 years ago

@citypaul that's basically all that simulate does. The simulation you want would be great, but neither enzyme's nor react-test-renderer's simulate has ever actually done that.

enzyme is for unit tests, not for browser-level integration tests like you're describing.

mcshakes commented 5 years ago

@ljharb @citypaul Sooo... based on this:

enzyme is for unit tests, not for browser-level integration tests like you're describing.

If I wanted to simulate a click event, followed by a life cycle event, a component conditional render based on what the user chose in the drop down, and a form submission, I shouldn't be using Enzyme?

Sorry if these are silly questions. I just got started using Enzyme, and it's been fun until this wall; but, if it's the wrong tool for the job, I should be looking for that tool instead.

ljharb commented 5 years ago

@mcshakes what are you trying to test with that, though? You don't need to test React, or the browser - if the onClick function is present, and does the right thing when invoked, your test is done.

victor-ono commented 4 years ago

Include select name in target:

wrapper.find('select[name="mySelect"]').simulate('change', {
  target: {
    name: 'mySelect',
    value: '1',
  },
})

expect(wrapper.find('select[name="mySelect"]').prop('value')).toEqual('1')
596050 commented 4 years ago

@ljharb Hello,

What if you are trying to test a form with conditional logic which hides/shows fields in the form? Should Enzyme be used in this case?

ljharb commented 4 years ago

@596050 absolutely. shallow render, and assert the presence or absence of various other components.