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

Setting text value of input #76

Closed
jackfranklin opened this issue Dec 16, 2015 · 74 comments
Closed

Setting text value of input #76

jackfranklin opened this issue Dec 16, 2015 · 74 comments

Comments

@jackfranklin
Copy link
Contributor

To set the value of an input currently I have to do:

const form = mount(<MyComponent />);
const input = form.find('input').get(0);
input.value = 'Blah blah';

It would be really nice in my opinion if I could instead do something akin to jQuery:

form.find('input').text('Blah blah')
// or probably better
form.find('input').value('Blah blah')

What are your thoughts on that? I'd be happy to attempt to work on the PR :)

@lelandrichardson
Copy link
Collaborator

@jackfranklin the purpose of enzyme is mostly to assert things about rendered react components and their behavior.... by manipulating input values you are manipulating the behavior of the component in an unpredictable way...

Some things you can do:

  1. if the input is getting rendered with a prop or state variable, try using the .setProps() or .setState() methods of enzyme.
  2. try simulating keypresses using .simulate('keydown', { which: 'a' }) or similar

@jackfranklin
Copy link
Contributor Author

@lelandrichardson understood, thanks. I quite like the idea of simulating key presses as a replacement. Would you consider a method to help input lots of keypresses? Eg .simulateKeyPresses('hello') (or whatever) that would call simulate with keydown a bunch of times.

@lelandrichardson
Copy link
Collaborator

I think that's an interesting idea! The fact that simulating typing is so difficult has been something that's bothered me...

@jackfranklin
Copy link
Contributor Author

OK - I might have a play and ping a PR if I get anywhere? :)

@jackfranklin
Copy link
Contributor Author

@lelandrichardson I had a go at this and ended up with something like this:

  simulateKeyPresses(characters, ...args) {
    for(let i = 0; i < characters.length; i++) {
      this.simulate('keyPress', extend({
        which: characters.charCodeAt(i),
        key: characters[i],
        keyCode: characters.charCodeAt(i)
      }, args));
    }
  }

This works in terms of calling an onKeyPress handler if you call it correctly, however the value doesn't actually change, and I'm not so sure as to why.

Additionally, the React test util docs do suggest you should set value and then simulate a change event:

// <input ref="input" />
var node = this.refs.input;
node.value = 'giraffe'
ReactTestUtils.Simulate.change(node);

So I would come back again to the point that it would be nice to have a syntactic method for setting the input value and triggering a change - although I can see why you want to avoid that. I'd love to come up with a simulateKeyPresses similar method that works correctly - any ideas you have would be really cool.

Thanks for a great library :)

@macalinao
Copy link
Contributor

Thanks so much for these examples. It'd be great if you could add .value(...) -- @jackfranklin have you been working on this?

@ljharb
Copy link
Member

ljharb commented Jan 18, 2016

I feel like a better approach here would be making a generic and entirely separate library that provided an API, that generated a stream of simulated browser events. Then, enzyme could simply use that library, and could delegate any API decisions to that other module. That other module would then be useful for a number of other projects.

I don't know what that API would look like, but I think solving it in a generic way, for all events and element types, is a hard enough problem that it shouldn't be attempted directly inside a larger project.

@vesln
Copy link
Contributor

vesln commented Jan 20, 2016

@ljharb i agree.

we felt the same pain and ended up building a capybara-like library that takes care of interactions

all: any thoughts on the scope/api/wishlist of such project will be highly appreciated, as i'd love to open source it

@SpencerCDixon
Copy link

@vesln have you made any progress on a capybara like library?

@vesln
Copy link
Contributor

vesln commented Feb 10, 2016

@SpencerCDixon i've built something that we use internally, but truth to be told, it's far away from ready.

i was really hoping for some input from the community on potential API and feature set before i open source it

if you have any suggestions/ideas please let me know

@SpencerCDixon
Copy link

gotcha. Yeah I havn't really spent much time thinking about it but I think it would be cool to mimic capybara's API as much as possible so the library would be very intuitive for people to learn who are coming from a Rails/Ruby background.

I'd definitely be down to put some time into it/help you work on it if you were interested in open sourcing what you did for your company.

@vesln
Copy link
Contributor

vesln commented Feb 10, 2016

@SpencerCDixon sounds awesome, let's join forces! i will ping u when i have something up, even if it's only the initial boilerplate. then we can discuss the api/features publicly and make it happen

@SpencerCDixon
Copy link

👍 sounds great!

@hnry
Copy link

hnry commented Feb 19, 2016

In regards to the original topic (not specific to keypress events) this does seem unintuitive. I just started using enzyme today so maybe there is a easier way of writing this out...

Currently if I wanted to test some event on a input, let's say change event, it looks like this:

    input.get(0).value = 'text'       // have to get the node...
    input.first().simulate('change')  // have to use the wrapper...

Versus the TestUtils way:

  node.value = str
  TestUtils.Simulate.change(node)

The unintuitive part is using 2 different types to do 1 action. Where as with the React TestUtils you're only having to get and deal with the node.

Again new to this, so is there a more streamlined way of doing this common testing task? Right now I'm doing it the TestUtils way.

@levibuzolic
Copy link

Not sure if this helps, but personally I've been using:

wrapper.find('input').simulate('change', {target: {value: 'My new value'}});

to test onChange methods and this has worked quite well for me.

@kwhitaker
Copy link

@levibuzolic your method worked great for me.

@boyney123
Copy link

@hnry Are you still using the TestUtils way?

@mrgreenh
Copy link

mrgreenh commented Apr 28, 2016

I feel like this could be related to the bug mentioned in this blog post:
http://ianmcnally.me/blog/2015/7/22/react-component-change-testing-works
So that if that was fixed enzyme would be as intuitive.

@eduardonunesp
Copy link

It's not working for textarea element

@MastroLindus
Copy link

It also doesn't work for components using currentTarget instead of target, (you cannot pass custom data to currentTarget the way you do it for target, currentTarget will ignore it)

@Kamaraju333
Copy link

@levibuzolic Is there a way if i could only set value , i dont have any Onchange or on Blur methods , its just

i just need to set value to the input

@takkyuuplayer
Copy link

@Kamaraju333

i just need to set value to the input

How about below?

const wrapper = mount(<input />);
wrapper.find('input').node.value = 'Test';

@frontmesh
Copy link

frontmesh commented Dec 27, 2016

Anyone agrees that @levibuzolic solution should be added to documentation? One has to dig trough all the comments here, to find a how to properly simulate input change .. Something this trivial should feel like more fun to accomplish.

@jDeppen
Copy link

jDeppen commented Jan 7, 2017

textarea works for me, @eduardonunesp.

I didn't think @levibuzolic's solution was working, but I had to swap mockStore with createStore to work for my situation.

import { reducer as formReducer } from 'redux-form'
...
let commentCreate
let form
beforeEach(() => {
  commentCreate = sinon.spy()

  form = mount(
-   <Provider store={mockStore({ form: formReducer })}>
+   <Provider store={createStore(combineReducers({ form: formReducer }))}>
      <Container commentCreate={commentCreate} />
    </Provider>
  ).find('form')
})

it('valid and calls commentCreate', () => {
  const textarea = form.find('textarea').first()
  textarea.simulate('change', { target: { value: 'foo' } })
  form.simulate('submit')
  expect(commentCreate.callCount).to.equal(1)
})

it('invalid so does not call commentCreate', () => {
  form.simulate('submit')
  expect(commentCreate.callCount).to.equal(0)
})

I used Redux-Form Test to troubleshoot.

@george-norris-salesforce
Copy link

george-norris-salesforce commented Jan 19, 2017

not working for me..

const input = wrapper.find('#my-input');

input.simulate('change',
  { target: { value: 'abc' } }
);

const val = input.node.value;

//val is ''

http://stackoverflow.com/questions/41732318/test-setting-text-value-with-react-and-enzyme

@ljharb
Copy link
Member

ljharb commented Jun 29, 2018

Indeed; in v3, all sub-wrappers must be re-found from the root to see updates.

@teatimes
Copy link

teatimes commented Jul 4, 2018

I managed to simulate setting a value for textarea.

wrapper.find("textarea#message").element.value = "a value" wrapper.find("textarea#message").trigger("input")

@ferrazrx
Copy link

The solution I found for having a input with ref to get the value instead of onChange, for example:

<input ref={(value)=>{this._email = value}} >

is to use .instance().value to add a value to the input, submitting the form if you want, and testing the value, like so:

wrapped.find('input[type="email"]').instance().value = "test";
 expect(wrapped.find('input[type="email"]').instance().value).toEqual("test");

@timbroder
Copy link

This is working for me to test filling out the password field on a login form

    const passwordInput = component.find('input').at(1);
    passwordInput.instance().value = 'y';
    passwordInput.simulate('change');

    component.find('form').first().simulate('submit');

@yuritoledo
Copy link

yuritoledo commented Sep 29, 2018

this works for me:

wrapper.find('#input-value').simulate('change', { target: { '10' } })

then, to check if the value was changed:

expect(wrapper.find('#input-value').props().value).toEqual('10')

@ljharb
Copy link
Member

ljharb commented Oct 1, 2018

It seems like a number of people have working solutions.

@jackfranklin happy to reopen if this is still an issue for you on latest enzyme.

@ljharb ljharb closed this as completed Oct 1, 2018
@martabacc
Copy link

@jsonmaur, how did you clear the mock on the afterEach / afterAll block?

@CWSites
Copy link

CWSites commented Dec 14, 2018

@yuritoledo I get Unexpected token on the closing } immediately after '10'

@timbroder, your version works however I'm unable to expect against the value prior to the change. How did you accomplish that?

@yuritoledo
Copy link

@CWSites Sorry, my bad. The correct is:
wrapper.find('#input-value').simulate('change', { target: '10'})

@anupammaurya
Copy link

anupammaurya commented Jan 2, 2019

here is my code..

const input = MobileNumberComponent.find('input')
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.

@yuritoledo
Copy link

yuritoledo commented Jan 2, 2019

@anupammaurya can you create a codesandbox ?

@timbroder
Copy link

@CWSites I haven't tried expecting before changing it. The value at that point is a known assumption from the test setup

@CWSites
Copy link

CWSites commented Jan 2, 2019

@timbroder what I'm running into is that it only updates the value of instance() and that value is not available to my function that is being called onChange. I still haven't been able to accomplish adjusting the value of an input. Currently I'm serving the value from state so I have to manually alter the state and use that to control the value of my input.

it('validateEmail', () => {
	const wrapper = mount(<Login history={[]} />);
	const emailUpdated = jest.spyOn(wrapper.instance(), 'emailUpdated');
	const validateEmail = jest.spyOn(wrapper.instance(), 'validateEmail');

	const loginButton = wrapper.find('Button');
	const emailInput = wrapper.find('Input');

	wrapper.setState({ email: 'invalid email' });

	// simulate login with invalid email
	loginButton.simulate('click');
	expect(validateEmail).toHaveBeenCalledTimes(1);
	expect(wrapper.state().invalid).toEqual(true);

	// since I can't update the value directly, update state directly
	wrapper.setState({ email: '[email protected]' });

	// simulate login with valid email
	emailInput.simulate('change', emailInput);
	expect(emailUpdated).toHaveBeenCalledTimes(1);
	expect(wrapper.state().invalid).toEqual(false);
	expect(wrapper.state().email).toEqual('[email protected]');
});

These are the different ways that I've tried to update the input, but none of them pass the value to the function which is called onChange.

// value is not passed to `emailUpdated`
emailInput.instance().value = '[email protected]';
expect(emailInput.instance().value).toEqual('[email protected]'); // returns '[email protected]'
expect(emailInput.props().value).toEqual('[email protected]'); // returns ''
expect(emailInput.value).toEqual('[email protected]'); // returns undefined

// value is not passed to `emailUpdated`
emailInput.props().value = '[email protected]';
expect(emailInput.props().value).toEqual('[email protected]'); // returns '[email protected]'
expect(emailInput.value).toEqual('[email protected]'); // returns undefined

// value is not passed to `emailUpdated`
emailInput.value = '[email protected]';
expect(emailInput.value).toEqual('[email protected]'); // returns '[email protected]'

@CeamKrier
Copy link

This is working for me to test filling out the password field on a login form

    const passwordInput = component.find('input').at(1);
    passwordInput.instance().value = 'y';
    passwordInput.simulate('change');

    component.find('form').first().simulate('submit');

I currently have tested this approach and works flawlessly

@ramdadam
Copy link

ramdadam commented May 13, 2019

This is working for me to test filling out the password field on a login form

    const passwordInput = component.find('input').at(1);
    passwordInput.instance().value = 'y';
    passwordInput.simulate('change');

    component.find('form').first().simulate('submit');

I currently have tested this approach and works flawlessly

I tried this out and typescript would complain about value not existing in instance()
This worked for me:

const attribute = document.createAttribute("value");
attribute.value = "[email protected]";
const emailInput = wrapper.find('input').at(0);
emailInput.getDOMNode().setAttributeNode(attribute);
emailInput.simulate('change');
wrapper.update();

@glassdimly
Copy link

glassdimly commented May 19, 2019

This is what worked for me for simulating text entry into a textarea and testing for a secondary component that was loaded after text entry.

RTFM simulate. Also note that simulate is on the wrapper (the result of find), not on the dom element (which would come from find('myThing').getDOMNode).

it('mounts and comment text entry triggers suggestion menu open with proper results', () => {
    const comment = mount(<Comment />);
    // Create a fake event with the values needed by handleOnChange
    const inputEvent = {persist: () => {}, target: { value: '@pdixon1'}};
    comment.find('textarea#commentTextArea').simulate('change', inputEvent);
    // Make sure to wait before testing for a dom result.
    setTimeout(function(){
        const suggestions = comment.find('#nameSuggestionsMenuList');
        expect(suggestions.length).toBeGreaterThan(0);
        expect(suggestions[0].id).toEqual('pdixon1');
    }, 1000);
});

@edwardfxiao
Copy link

For anyone who failed to get the value right with wrapper.find('input').simulate('change', {target: {value: 'My new value'}});

Here is what I did and it is working

const SomeInputComponent = ({onChangeCallback}) => {
    const inputRef = useRef(null);
    const handleOnChange = useCallback(()=>{
        onChangeCallback(inputRef.current.value)
    }, [])

return <div><input ref={inputRef} onChange={handleOnChange} /></div>
}
  it('Should change the value ', () => {
    let value = '';
    const wrapper = mount(
      <SomeInputComponent
        onChangeCallback={res => {
          value = res;
        }}
      />,
    );
    const $input = wrapper.find('input');
    $input.at(0).instance().value = 'Changed';
    $input.simulate('change');
    expect(value).toEqual('Changed');
 });

@misstricky
Copy link

This is working for me to test filling out the password field on a login form

    const passwordInput = component.find('input').at(1);
    passwordInput.instance().value = 'y';
    passwordInput.simulate('change');

    component.find('form').first().simulate('submit');

This is the only thing that seems to work at all with a password input type -- thank you!

@sypl
Copy link

sypl commented Aug 14, 2019

This is working for me to test filling out the password field on a login form

    const passwordInput = component.find('input').at(1);
    passwordInput.instance().value = 'y';
    passwordInput.simulate('change');

    component.find('form').first().simulate('submit');

This is the only thing that worked for me on a controlled input. Thanks!

@Puneet1796
Copy link

Not sure if this helps, but personally I've been using:

wrapper.find('input').simulate('change', {target: {, value: 'My new value'}});

to test onChange methods and this has worked quite well for me.

I've been using this too, but I want to ask that what if I'm passing data in the onChange like this

onChange={e => this.myOnChange(e, data)}

Now, I'm confused about how can I pass data in the onChange on the test?

@ljharb
Copy link
Member

ljharb commented Oct 16, 2019

@Puneet1796 that depends on where data comes from. You can't alter that from just having access to the onChange prop.

@Puneet1796
Copy link

@ljharb How does it matter?, According to the documentation simulate on mounted components can't let data pass through it, now here what enzyme fails to test the onChange.

@ljharb
Copy link
Member

ljharb commented Oct 22, 2019

@Puneet1796 i'm not sure what you mean - it matters because the way the JS language works prevents any interception of data there.

@Puneet1796
Copy link

@ljharb I have an array of values and I'm iterating over each value and renders a component everytime, say list item, but I want to pass index in the change so that I'll know which list item is updated. But in the case of testing, I found out that there's no way mentioned in the documentation to pass the value in onChange via simulate.
The value that I want to pass is the index of that particular list item.

@ljharb
Copy link
Member

ljharb commented Oct 23, 2019

Based on the code you passed, it's impossible via any means.

@cphoover
Copy link

cphoover commented Jan 8, 2020

What i had to do to update the value of a form field that was only referenced with a ref.

const inputField = wrapper.find('input[type="text"]');
inputField.getDOMNode().value = "my updated value";
inputField.simulate('whatever you are listening for');

@m8ms
Copy link

m8ms commented Apr 14, 2020

This is working for me to test filling out the password field on a login form

    const passwordInput = component.find('input').at(1);
    passwordInput.instance().value = 'y';
    passwordInput.simulate('change');

    component.find('form').first().simulate('submit');

this one actually worked for me;

All other solutions in this thread gave different errors of the no function x of undefined sort (including and especially the doc's way with simulate('change', { target: /* ... */ }))

@grifterlv
Copy link

I have tried all the methods mentioned above but none of them worked for me.

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

No branches or pull requests