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

userEvent.selectOptions does not trigger onChange event #358

Closed
khalidwilliams opened this issue Jun 16, 2020 · 16 comments · Fixed by #370
Closed

userEvent.selectOptions does not trigger onChange event #358

khalidwilliams opened this issue Jun 16, 2020 · 16 comments · Fixed by #370
Assignees
Labels
bug Something isn't working released

Comments

@khalidwilliams
Copy link

  • @testing-library/user-event version: 12.0.0
  • Testing Framework and version: jest@24.7.1
  • DOM Environment: jsdom@14.1.0

Relevant code or config

// Select.js
import React from "react";

const Select = props => {
  const handleChange = e => {
    console.log(e.target.value);
    props.test(e.target.value);

  };
  return (
    <select data-testid="select" onChange={handleChange}>
      <option data-testid="first" value="first">
        first
      </option>
      <option data-testid="second" value="second">
        second
      </option>
      <option data-testid="third" value="third">
        third
      </option>
    </select>
  );
};
export default Select;

// Select.test.js
  import React from 'react';
  import Select from './Select';
  import userEvent from '@testing-library/userEvent';
  import render from '@testing-library/react';


  test("`userEvent.selectOptions` should trigger an onChange event", () => {
    const mockTest = jest.fn().mockImplementation(val => console.log(val));
    const { getByTestId, debug } = render(<Select test={mockTest} />);
    const select = getByTestId("select");
    const second = getByTestId("second");

    userEvent.selectOptions(select, ["second"]);
    debug();
    console.log(select.value)
    expect(mockTest).toHaveBeenCalledTimes(1);
    expect(mockTest).toHaveBeenCalledWith('second');
    expect(second.selected).toBe(true);
  });

What you did:
I first noticed this behavior in a larger project, so I made a small repo to reproduce.

I made a <select/> element in React that fires a function received from props onChange. This behavior works as expected in the browser. When testing the behavior, I can successfully assert that a mocked version of that onChange function is called when I trigger a change event (via importing fireEvent from RTL), but cannot assert that the same mock is called when using userEvent.selectOptions.

What happened:
Screen Shot 2020-06-16 at 9 28 24 AM

Reproduction repository:
https://github.com/khalidwilliams/select-test-demo

Problem description:
userEvent.selectOptions doesn't trigger a change event. onChange handlers won't fire in tests that use this method, leading to problems with testing any change-dependent behavior.

Suggested solution:
I'm not sure what the best solution is, it seems like this line may not be firing as expected? Please let me know if there's something I'm missing here -- I'm happy to keep digging!

@herecydev
Copy link
Contributor

Thanks for opening this Khalid, experienced the same today and was going to put a repro together as well.

If I had to guess, the following PR would have caused it: #348

@herecydev
Copy link
Contributor

Looks like fireEvent.click(option, init) is called when using multiple but not when a single option is supplied. That might be the error

@nickmccurdy nickmccurdy added the bug Something isn't working label Jun 16, 2020
@khalidwilliams
Copy link
Author

@herecydev, how did you verify that clicks aren't going through when called with a single option? When I edit the test and implementation files to try multiple and non-multiple select elements (pushed up here, I can log information on click events in either case, but cannot log anything on change. Adding some screenshots of the results below:

Screen Shot of single-select test result

Screen Shot of multi-select test result

@marcosvega91
Copy link
Member

I think that the problem here is that React will trigger change on the bubbling phase but as I see in the code
fireEvent(select, createEvent('change', select, init)) the event is created from scratch with bubbles to false

@jamsinclair
Copy link

jamsinclair commented Jun 19, 2020

@marcosvega91 great debugging!

If you need a workaround you could update affected tests to override the event properties.

userEvent.selectOptions(select, ["second"], { bubbles: true });

Curious as to why we're not using aliased event functions e.g. fireEvent.change, which defaults bubbles to true. Must be related to/regressed from the attempted move over to dom-testing-library.

@marcosvega91
Copy link
Member

marcosvega91 commented Jun 19, 2020

I think that @kentcdodds can help us to understand why is not called fireEvent.change 😸

@kentcdodds
Copy link
Member

It's because the browser doesn't call the change event. It calls a generic event with the type of change (like we do).

@marcosvega91
Copy link
Member

marcosvega91 commented Jun 19, 2020

ok I understand, but in any case we should call the event with bubbles=true right?

here is the event playground in vanilla version.
the onChange is called with bubbles:true

here is a pseudo playground in react.
As above the onChange is called with bubbles:true

maybe I'm wrong in something

@kentcdodds
Copy link
Member

Yes, you're correct.

It just occurred to me that the event with type change can actually use fireEvent.change because it's constructor is Event, but we can't use fireEvent.input because it's constructor is InputEvent and that's not what the browser uses for this interaction.

So all that said, I think that we should change this code so it uses fireEvent.change for the change event (that'll make it bubble properly), and then we should add these properties to the init for the input event: {bubbles: true, cancelable: false, composed: true}.

A pull request to do this would be welcome :)

@marcosvega91
Copy link
Member

Yes of course I'll open a PR for that. I'll love all these projects

@fabb
Copy link

fabb commented Jun 22, 2020

I just wanted to open an issue and saw there's already a PR for it, nice!

Here's my repro:
https://codesandbox.io/s/usereventselectoptions-bug-gn8wb?file=/src/__tests__/App.js

@kentcdodds
Copy link
Member

🎉 This issue has been resolved in version 12.0.7 🎉

The release is available on:

Your semantic-release bot 📦🚀

@arjun-js
Copy link

arjun-js commented Dec 1, 2021

It is happening now for select dropdowns.

I am using userEvent.selectOptions to change the value. I am able to change the value but the change event is not getting triggered.

I am using:

@testing-library/user-event : 13.5.0

@Bhuvanesh-git331
Copy link

const dbEle=document.getElementById('schemaname')
const optionEle=document.createElement('option')
optionEle.setAttribute('value', 'el_cardnumber_3')
dbEle.domNode = optionEle
dbEle.appendChild(optionEle);
console.log(screen.debug(document.querySelectorAll('option')[1]))
userEvent.selectOptions(dbEle, document.querySelectorAll('option')[1])

//Hi I tried the above method its working for me. The onChange event is getting triggered

@tcrz
Copy link

tcrz commented Dec 5, 2022

Hi @arjun-js I am facing the same issue currently. Did you find a solution yet?

@sKopheK
Copy link

sKopheK commented Dec 30, 2022

"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",

the example in OP still does not work, but in my case it's due to missing await before userEvent.selectOptions call

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working released
Projects
None yet
Development

Successfully merging a pull request may close this issue.