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

[docs][Rating] Testing the component with @testing-library/user-event results in NaN #38828

Open
IgnusG opened this issue Sep 6, 2023 · 11 comments · May be fixed by #45054
Open

[docs][Rating] Testing the component with @testing-library/user-event results in NaN #38828

IgnusG opened this issue Sep 6, 2023 · 11 comments · May be fixed by #45054
Assignees
Labels
component: rating This is the name of the generic UI component, not the React module! docs Improvements or additions to the documentation good first issue Great for first contributions. Enable to learn the contribution process. package: material-ui Specific to @mui/material ready to take Help wanted. Guidance available. There is a high chance the change will be accepted

Comments

@IgnusG
Copy link

IgnusG commented Sep 6, 2023

Steps to reproduce 🕹

Link to live example: https://codesandbox.io/p/sandbox/eloquent-breeze-7dtljr (run test task and scroll up for console log)

Steps:

  1. Install @testing-library/user-event and setup a Rating component with an onChange handler
  2. In a jest test use
const user = userEvent.setup();

user.click(screen.getByLabelText('2 Stars'))
  1. Observe the onChange handler is fired with newRating set to NaN

Current behavior 😯

Due to the way user events simulates user interaction the handleMouseMove method in MUI's Rating is fired (since user event simulates the mouse movement before issuing the click event). This method however relies on .getBoundingClientRect which is not available in jest - or rather it is but all the values are set to 0 which means the percent calculation tries to divide by 0 ((event.clientX - left) / (width * max)) which results in a NaN being set as the hover state.

In onChange since hover is set even though the parseFloat(event.target.value) parses correctly it is overridden by the NaN from hover.

Expected behavior 🤔

Either this edge case is documented - I didn't find anything about this handleMouseMove behavior and how it takes precedence over the click when running the onChange callback in the Rating docs or the calculation is adjusted to take into account that width could potentially be 0 and the hover should not be set (or be set to -1) in this case - such that onChange still reports the event.target.value instead.

Context 🔦

No response

Your environment 🌎

npx @mui/envinfo
System:
    OS: macOS 13.5
  Binaries:
    Node: 18.14.2 - ~/.asdf/installs/nodejs/18.14.2/bin/node
    Yarn: 1.22.19 - ~/.asdf/shims/yarn
    npm: 9.5.0 - ~/.asdf/plugins/nodejs/shims/npm
  Browsers:
    Chrome: 116.0.5845.140
    Edge: Not Found
    Safari: 16.6
  npmPackages:
    @emotion/react: 11.11.1 => 11.11.1
    @emotion/styled: 11.11.0 => 11.11.0
    @mui/base:  5.0.0-beta.0
    @mui/core-downloads-tracker:  5.12.2
    @mui/icons-material: 5.10.3 => 5.10.3
    @mui/lab: 5.0.0-alpha.73 => 5.0.0-alpha.73
    @mui/material: 5.10.3 => 5.10.3
    @mui/private-theming:  5.12.0
    @mui/styled-engine:  5.12.0
    @mui/system: 5.10.3 => 5.10.3
    @mui/types:  7.2.4
    @mui/utils: 5.10.3 => 5.10.3
    @mui/x-date-pickers: ^6.6.0 => 6.6.0
    @types/react: 18.2.13 => 18.2.13
    react: 18.2.0 => 18.2.0
    react-dom: 18.2.0 => 18.2.0
    typescript: 5.1.6 => 5.1.6
@IgnusG IgnusG added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Sep 6, 2023
@zannager zannager added the component: rating This is the name of the generic UI component, not the React module! label Sep 6, 2023
@mnajdova mnajdova assigned DiegoAndai and unassigned mj12albert Oct 25, 2023
@DiegoAndai
Copy link
Member

Hey @IgnusG, thanks for the report!

This method however relies on .getBoundingClientRect which is not available in jest - or rather it is but all the values are set to 0

This seems like a testing limitation. In Material UI's Rating tests, there's getBoundingClientRect stubbing: https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Rating/Rating.test.js#L44. Do you think that's a good enough workaround for your use case?

@DiegoAndai DiegoAndai added package: material-ui Specific to @mui/material status: waiting for author Issue with insufficient information and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Oct 26, 2023
Copy link

github-actions bot commented Nov 2, 2023

Since the issue is missing key information and has been inactive for 7 days, it has been automatically closed. If you wish to see the issue reopened, please provide the missing information.

@github-actions github-actions bot closed this as completed Nov 2, 2023
@IgnusG
Copy link
Author

IgnusG commented Nov 2, 2023

Hey @DiegoAndai 👋

Yeah I also resorted to stubbing to resolve this issue. Maybe MUI will handle this edge case more gracefully in the future but until then I think it's a good enough solution 👍

I would only suggest to add this gotcha to the Rating component's documentation. I couldn't find anything in there about this behavior and it took me a few hours of digging in the source code to find out this was the cause.

@github-actions github-actions bot removed the status: waiting for author Issue with insufficient information label Nov 2, 2023
@github-actions github-actions bot reopened this Nov 2, 2023
@ausram
Copy link

ausram commented May 28, 2024

In the meantime, for anyone else intimidated by the stubbing workaround, another way to avoid this issue when testing the component is to set the skipHover option to the userEvent setup:

const user = userEvent.setup({ skipHover: true })
user.click(screen.getByLabelText('2 Stars'))

@DiegoAndai
Copy link
Member

DiegoAndai commented Jun 4, 2024

I would only suggest to add this gotcha to the Rating component's documentation. I couldn't find anything in there about this behavior and it took me a few hours of digging in the source code to find out this was the cause.

I agree that documenting this would be useful. I will add the ready to take and good first issue to see if we get any contributions. The work would be to add a small section in the Rating component docs about stubbing/skipping hover when testing.

If anyone is interested on working on it, let us know 😊

@DiegoAndai DiegoAndai added docs Improvements or additions to the documentation test good first issue Great for first contributions. Enable to learn the contribution process. ready to take Help wanted. Guidance available. There is a high chance the change will be accepted and removed test labels Jun 4, 2024
@DiegoAndai DiegoAndai changed the title Testing the Rating component with @testing-library/user-event results in NaN [material-ui][docs][Rating] Testing the component with @testing-library/user-event results in NaN Jun 4, 2024
@rajabhi21
Copy link

@DiegoAndai can you please assign it to me? I'd like to work on it.

@DiegoAndai
Copy link
Member

@rajabhi21 sure! Go ahead. Let me know if you need any help. Thanks in advance.

@p-pych
Copy link

p-pych commented Oct 16, 2024

Hi @DiegoAndai as i've not seen any progress on this for last two weeks. I decided to try my luck. Please find attached PR. I probably forgot about the label :( . #44129

@oliviertassinari
Copy link
Member

oliviertassinari commented Oct 16, 2024

This is how the component is tested:

it('should select the rating', () => {
const handleChange = spy();
const { container } = render(<Rating name="rating-test" onChange={handleChange} value={2} />);
fireEvent.click(container.querySelector('input[name="rating-test"][value="3"]'));
expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.deep.equal(3);
const checked = container.querySelector('input[name="rating-test"]:checked');
expect(checked.value).to.equal('2');
});

So far we avoided @testing-library/user-event in the test as it's slower and hides events away, sometimes having wrong behavior too relative to the platform. In the reproduction, this works:

 // @vitest-environment jsdom

 import { Rating } from "@mui/material"
 import { render, fireEvent, screen } from "@testing-library/react"
 import userEvent from "@testing-library/user-event"

 import { describe, test } from 'vitest';

 describe('Rating', () => {
     test('Rating', async () => {
         const user = userEvent.setup();
         render(<Rating onChange={(_, newRating) => console.log('newRating', newRating)} />);
    
-        await user.click(screen.getByLabelText('2 Stars'));
+        fireEvent.click(screen.getByLabelText('2 Stars'));
     })
 })

https://codesandbox.io/p/devbox/wispy-fire-7lsnzs?file=%2Fsrc%2FApp.test.tsx%3A22%2C1

Now, yeah, I see @testing-library/user-event has been growing a bit in popularity: https://npm-stat.com/charts.html?package=%40testing-library%2Fuser-event&package=%40testing-library%2Fdom&from=2020-10-15&to=2024-10-15. Something from 43% to 59% in 4 years.

We have 1. docs as a lever, I think that we also have:

  1. Add a test with user.click to make sure it works and be something others can copy
  2. Consider special handling in the logic if getBoundingClientRect is not available, effectively for tests, we have a couple of those already in the codebase. Lately, we discussed this in [data grid] Tests fail with: "IntersectionObserver is not defined" after upgrading to x-data-grid v7.3.2 mui-x#12983 (comment)

On updating the docs 1., my instinct as a developer would be to go straight to check the source to see how it's tested, I wouldn't trust the docs so much because it doesn't run in the CI, it could get outdated. If we do 3. We don't need 1.

+1 on my end to do 3. then 2, and skip 1 because 3 might be a minimal overhead compared to the DX pain (not a strong preference, only #suggestion).

@oliviertassinari oliviertassinari changed the title [material-ui][docs][Rating] Testing the component with @testing-library/user-event results in NaN [docs][Rating] Testing the component with @testing-library/user-event results in NaN Oct 16, 2024
@NooBat
Copy link

NooBat commented Oct 21, 2024

Can I ask whether are we gonna go with 3. -> 2.? I'd love to work on this approach

@DiegoAndai
Copy link
Member

@NooBat, sorry for the delay. I agree with going with 3. and 2. If you (or anyone) wish to implement it, go ahead. Feel free to let me know if you need any help. Thanks in advance.

@NooBat NooBat linked a pull request Jan 18, 2025 that will close this issue
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: rating This is the name of the generic UI component, not the React module! docs Improvements or additions to the documentation good first issue Great for first contributions. Enable to learn the contribution process. package: material-ui Specific to @mui/material ready to take Help wanted. Guidance available. There is a high chance the change will be accepted
Projects
None yet
9 participants