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

Unable to change the value of form inputs #364

Open
MoonTahoe opened this issue May 5, 2016 · 32 comments
Open

Unable to change the value of form inputs #364

MoonTahoe opened this issue May 5, 2016 · 32 comments
Projects

Comments

@MoonTahoe
Copy link

First... let me mention that I love using enzyme. Great work. I am currently putting togther training materials for React that will use enzyme for testing.

Problem

I am having a problem testing a form. Specifically changing input values on the form.

wrapper.ref('first').simulate('change', {target: {value: 'David'}}); is not working
wrapper.find("input#first").simulate('change', {target: {value: 'David'}}); is not working

I have created a reduced test case that demonstrates the problem. The output for this example looks like:

Output

  Testing a form
first name: Mike
last name: Tyson
    ✓ can fill out the form

No matter what the default Mike Tyson is always the name, despite the fact that I am chanign it. Why does this happen? Is there another way to change the values for the form?

Test

import { Component } from 'react'
import { expect } from 'chai'
import { mount } from 'enzyme'

class TheForm extends Component {

    submit() {
        console.log('first name:', this.refs.first.value);
        console.log('last name:', this.refs.last.value);
    }

    render() {
        return (
            <form action="javascript:void(0)"
                  onSubmit={this.submit.bind(this)}>

                <div>
                    <input ref="first"
                           type="text"
                           placeholder="your first name..."
                           required="required"
                           defaultValue="Mike"/>
                </div>

                <div>
                    <input ref="last"
                           type="text"
                           placeholder="your last name..."
                           required="required"
                           defaultValue="Tyson" />
                </div>

                <button>ADD</button>

            </form>
        );
    }

}

describe("Testing a form", () => {

    it("can fill out the form", () => {
        const wrapper = mount(<TheForm />);
        wrapper.ref('first').simulate('change', {target: {value: 'David'}});
        wrapper.ref('last').simulate('change', {target: {value: 'Blane'}});
        wrapper.find('form').simulate('submit');
    });

});

also... wrapper.find('form').simulate('submit') works, but clicking the button does not. Even though clicking the button submits the form in the browser.

@aweary
Copy link
Collaborator

aweary commented May 5, 2016

also... wrapper.find('form').simulate('submit') works, but clicking the button does not. Even though clicking the button submits the form in the browser.

FYI this has been reported previously here: #308

@aweary
Copy link
Collaborator

aweary commented May 5, 2016

I'm able to reproduce your issue with the current release of enzyme, I'll look into how this is expected to work. For what it's worth, you can use controlled components to handle the input state, which works as you expected:

class TheForm extends React.Component {

    constructor(props) {
      super(props);
      this.state = {
        firstName: 'Mike',
        lastName: 'Tyson'
      }
      this.onFirstNameChange = this.onFirstNameChange.bind(this);
      this.onLastNameChange = this.onLastNameChange.bind(this);
    }

    submit() {
        console.log('first name:', this.state.firstName);
        console.log('last name:', this.state.lastName);
    }

    onFirstNameChange(e) {
      this.setState({
        firstName: e.target.value
      })
    }

    onLastNameChange(e) {
      this.setState({
        lastName: e.target.value
      })
    }

    render() {
        return (
            <form action="javascript:void(0)"
                  onSubmit={this.submit.bind(this)}>

                <div>
                    <input ref="first"
                           type="text"
                           placeholder="your first name..."
                           required="required"
                           onChange={this.onFirstNameChange}
                           value={this.state.firstName}/>
                </div>

                <div>
                    <input ref="last"
                           type="text"
                           placeholder="your last name..."
                           required="required"
                           onChange={this.onLastNameChange}
                           value={this.state.lastName} />
                </div>

                <button>ADD</button>

            </form>
        );
    }

}

Controlled components are typically recommended as well: https://facebook.github.io/react/docs/forms.html

@MoonTahoe
Copy link
Author

Yea, thanks. The issue, in my case is that I cannot change the TheForm, I only have access to the test for the form.

@MoonTahoe
Copy link
Author

I was able to create a test that passed based on what I found in issue #76.

describe("Testing a form", () => {

    let wrapper;

    before(() => {
        wrapper = mount(<TheForm />);
        wrapper.ref('first').get(0).value = 'David';
        wrapper.ref('last').get(0).value = 'Blane';
        wrapper.find('form').simulate('submit');
    });

    it("can set first name", () => expect(wrapper.ref('first').get(0).value).to.equal("David"));

    it("can set last name", () => expect(wrapper.ref('last').get(0).value).to.equal("Blane"));

});

It would be nice if there were a cleaner way to do this as requested in Issue #76

@aweary
Copy link
Collaborator

aweary commented May 6, 2016

@MoonTahoe I'm curious, does this work?

wrapper.find('input').first().simulate('change', {target: {value: 'David'}});`

@aweary
Copy link
Collaborator

aweary commented May 6, 2016

@MoonTahoe also, following the example of ReactTestUtils.Simulate.change, this should work:

const wrapper = mount(<TheForm/>);
const first = wrapper.find('first');
first.node.value = 'David'
first.simulate('change', first)

@ffxsam
Copy link

ffxsam commented May 13, 2016

I also ran into issues getting and setting values on <input /> tags. input.node.value works great, but I'm curious why this doesn't work:

    const wrapper = mount(<EditableText defaultValue="Hello" />);
    const input = wrapper.find('input');

    console.log(input.render().val());

https://stackoverflow.com/questions/37219772/enzyme-how-to-access-and-set-input-value

@tarjei
Copy link

tarjei commented Jun 12, 2016

Hi, I'm sorry for hijacking this thread, but the examples listed here should go into the documentation. Form handling is badly documented (IMHO).

Also, how about a setValue(value, triggerChange=true) method that could work on select and input? I.e. you could use the method to both set a value and emit the change event. this would improve usability for formhandling a lot.

Then you can do wrapper.find('input').setValue("val") instead of a three line bolierplate invocation.

T

@ghost
Copy link

ghost commented Jun 17, 2016

using the solution proposed by @aweary, I get TypeError: Can't add property value, object is not extensible

@aweary
Copy link
Collaborator

aweary commented Jun 17, 2016

@Moezalez what testing environment are you using (jsdom, karma, etc.)?

@ghost
Copy link

ghost commented Jun 17, 2016

@aweary nyc + mocha

@aweary
Copy link
Collaborator

aweary commented Jun 17, 2016

@Moezalez so you're using jsdom? What version of React and what version of enzyme? Can you share a small reproducable case?

@whitejava
Copy link

whitejava commented Oct 4, 2016

This works fine for me:

// <div><input/></div>
let a = mount(<TestComponent/>);
a.find('input').node.value = 'Task1';

@bchenSyd
Copy link

just to help people who are struggling with changing input value (and do some further checks, like validation). here is something that works for me after I read through this post

 it('should display business trade name correctly', () => {
        const business_trade_name = wrapper.findWhere(n => n.name() === 'Field'
            && n.prop('name') === 'business_trade_name')
        expect(business_trade_name).toExist('cannot find business_trade_name. did you accidently renamed the field?')


        const input = business_trade_name.find('input')

        input.node.value='bo chen**&&))'
        input.simulate('change', input)

        wrapper.find('form').simulate('submit')

       const errorSpan = business_trade_name.find('span')
       expect(errorSpan.text()).toBe('Invalid business trade name')


    })


my rendered dom tree is like

<form>
       <Field name='business_trade_name'>
           <input />
          <span />
      </Field>

      <Field name='another_field'>
         <input />
        <span />
      </Field>
</form>

and yes, i'm using redux-form

@julia-kits
Copy link

@bochen2014 thanks so much, your solution is the only one working for me! you really helped me!

@erika-dike
Copy link

@bochen2014 another +1 for you. Thanks a lot. Your solution alone worked for me.

@Rob-Leggett
Copy link

Rob-Leggett commented Jan 29, 2017

The following worked for me also.

const username = wrapper.ref('username')
username.node.value='test'
username.simulate('change', username)

However I am disappointed the following did not work.

wrapper.ref('username').simulate('change', {target: {value: 'test'}})

@ConAntonakos
Copy link

ConAntonakos commented Apr 26, 2017

@aweary's solution worked for my testing purposes, yet I still don't know why. 😉

@nerfologist
Copy link

Hi, I opened PR #995 adding a test case to (hopefully) demonstrate this issue.

@Falieson
Copy link

Falieson commented Aug 8, 2017

I also am getting errors related to this subject (simulate('change' ...) not causing a change)

test('TaskHeader changes Value after Inputted', () => {
  const component = mount(<TaskHeader listName="Demo" totalTasks={55} />)

  const taskInput = component.find('.app-tasks-newTask-input')
  expect(taskInput.props().value).toEqual('')

  // const event =  { target: { value: 'First Task' } }
  
  taskInput.props().onChange({currentTarget: {value: 'First Task'}}) // NOTE: this works
  expect(taskInput.props().value).toEqual('First Task')

  // taskInput.simulate('change',{target: {value: 'Second Task'}}) // NOTE: doesn't work
  taskInput.node.value='Second Task'
  taskInput.simulate('change', taskInput) // NOTE: works as a replacement
  expect(taskInput.props().value).toEqual('Second Task')
})

@ljharb
Copy link
Member

ljharb commented Aug 8, 2017

It's not meant to cause a change; it's mean to call the onChange function.

I recommend avoiding simulate, and manually invoking the prop function you want instead.

@Falieson
Copy link

Falieson commented Aug 8, 2017

Good to know, thanks - I probably spent an hour trying to hunt down the solution w/o anyone saying that a particular way is the better way to do it.

@DannyRyman
Copy link

Hmm, using node.value no longer works "Attempted to access ReactWrapper::node, which was previously a private property on Enzyme ReactWrapper instances, but is no longer and should not be relied upon. Consider using the getElement() method instead". Is there an alternative way of setting the value?

@ljharb
Copy link
Member

ljharb commented Sep 27, 2017

There's a getNode function.

@bjudson
Copy link

bjudson commented Sep 28, 2017

Using mocha 3, react 16, and enzyme 3, this worked for me:

wrapper.find('input').instance().value = "foo";

@igorify
Copy link

igorify commented Oct 6, 2017

Thanks @bjudson .
Start using
wrapper.find('input').instance().value = "foo"
instead of
wrapper.find('input').node.value = "foo",

because according to documentation, node method is private in enzyme3 https://airbnb.io/enzyme/docs/guides/migration-from-2-to-3.html#private-properties-and-methods-have-been-removed

@abhinavsingi
Copy link

seems better than #76 (comment)
and also works
as mentioned above - wrapper.find('input').instance().value = "foo"

@Quadriphobs1
Copy link

I am trying this with a redux state component, The dispatch is working as I can confirm from the action logging out the output but the reducer is not working as I cant seems to get a logged out output at all
Any idea what could be wrong

@engmyahya
Copy link

This worked with me:

it("Successfully add an employee to the employees' list when the form submitted",function(){
   const wrapper = mount(<App/>);
   const addEmpWapper = wrapper.find('AddEmployee');
   addEmpWapper.find("#txtName").getDOMNode().value = "Youki";
   addEmpWapper.find("#txtImgUrl").getDOMNode().value = "ImageUrl1";
   addEmpWapper.find("#txtDesc").getDOMNode().value = "Cute baby.";

   const form = addEmpWapper.find('form');
   form.simulate("submit");
   // I already had 3 employees in the app's states bag.
   expect(wrapper.state().employees).to.have.lengthOf(4);
 });

@saicharanp
Copy link

@engmyahya Your code throws a typescript error for me - Property value does not exist on type Element

@anupammaurya
Copy link

here is my code..

const input = MobileNumberComponent.find('input')
// when
input.props().onChange({target: {
   id: 'mobile-no',
   value: '1234567900'
}});
MobileNumberComponent.update()
const Footer = (loginComponent.find('Footer'))
expect(Footer.find('Buttons').props().disabled).equals(false)

I have update my DOM with componentname.update()
And then checking submit button validation(disable/enable) with length 10 digit.

@Joshua-rose
Copy link

@engmyahya Your code throws a typescript error for me - Property value does not exist on type Element

You need to cast the elements as HTMLInputElement
i.e.

it("Successfully add an employee to the employees' list when the form submitted",function(){
   const wrapper = mount(<App/>);
   const addEmpWapper = wrapper.find('AddEmployee');

   const txtName = addEmpWapper.find("#txtName").getDOMNode() as HTMLInputElement;
   txtName.value = "Youki";
   const txtImgUrl = addEmpWapper.find("#txtImgUrl").getDOMNode() as HTMLInputElement
   txtImgUrl.value = "ImageUrl1";
   const txtDesc = addEmpWapper.find("#txtDesc").getDOMNode() as HTMLInputElement
   txtDesc.value = "Cute baby.";

   const form = addEmpWapper.find('form');
   form.simulate("submit");
   // I already had 3 employees in the app's states bag.
   expect(wrapper.state().employees).to.have.lengthOf(4);
 });

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

No branches or pull requests