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

Testing select changes? #389

Closed
dijs opened this issue May 13, 2016 · 36 comments
Closed

Testing select changes? #389

dijs opened this issue May 13, 2016 · 36 comments
Labels

Comments

@dijs
Copy link

dijs commented May 13, 2016

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
Copy link

sattaman commented May 13, 2016

We have just been facing a similar issue.

We found an existing issue:
#218

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

@dijs
Copy link
Author

dijs commented May 13, 2016

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

@dijs
Copy link
Author

dijs commented May 13, 2016

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
Copy link
Author

dijs commented May 13, 2016

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
Copy link
Author

dijs commented May 17, 2016

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.

@dijs dijs closed this as completed May 17, 2016
@aweary aweary added the invalid label May 17, 2016
@schovi
Copy link

schovi commented Aug 18, 2016

@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
Copy link

I am still unable to test select changes

@knuterik91
Copy link

knuterik91 commented Sep 29, 2016

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
Copy link

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

@knuterik91
Copy link

@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
Copy link

fernandopasik commented Oct 17, 2016

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

@NealEhardt
Copy link

NealEhardt commented Dec 21, 2016

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
Copy link
Member

ljharb commented Dec 21, 2016

@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
Copy link

@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
Copy link
Member

ljharb commented Dec 21, 2016

@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
Copy link

sivakumarbankuru commented Mar 31, 2017

[[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
Copy link

aakarim commented Jun 2, 2017

const component = shallow(
      <ComponentWithCustomSelectComponentAsChild
        id="1"
        name="test"
      />,
    );
component.find(ImportedCustomSelectComponent).simulate('select', 'test');      
expect(component.state('selectHandlerValueThatChangesStateValue')).toEqual('test');

works for me

@suwi
Copy link

suwi commented Oct 24, 2017

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

@ljharb
Copy link
Member

ljharb commented Oct 24, 2017

@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
Copy link

unfalse commented Dec 12, 2017

None of the solutions presented here is working for me.

@betterkenly
Copy link

betterkenly commented Mar 7, 2018

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
Copy link

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
Copy link

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
Copy link

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')

@mikefrancis
Copy link

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
Copy link

wsmd commented Jul 1, 2018

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
Copy link

mrampiah commented Jul 3, 2018

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
Copy link

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
Copy link
Member

ljharb commented Jan 29, 2019

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

@citypaul
Copy link

@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
Copy link
Member

ljharb commented Jan 29, 2019

@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
Copy link

@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
Copy link
Member

ljharb commented Jan 29, 2019

@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
Copy link

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
Copy link

596050 commented Oct 17, 2019

@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
Copy link
Member

ljharb commented Oct 18, 2019

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

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

No branches or pull requests