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 click on stars in React testing library. #54

Open
muhammadali-HazelSoft opened this issue Dec 13, 2023 · 3 comments
Open

Unable to click on stars in React testing library. #54

muhammadali-HazelSoft opened this issue Dec 13, 2023 · 3 comments

Comments

@muhammadali-HazelSoft
Copy link

How can i clicks on the star rating. I tried so much.
<div className="text-center mt-3"> <Rating initialValue={rating} onClick={handleRating} fillColor={surveyTheme?.mainThemeColor || "#F1A900"} fillIcon={ <RxStarFilled size={50} aria-label="filled-stars" role="button" /> } emptyIcon={ <RxStar size={50} color={surveyTheme?.mainThemeColor || "#F1A900"} aria-label="unfilled-stars" role="button" /> } allowHover={false} aria-label="ranking-stars" as="button" /> </div>

@Parassharmaa
Copy link

AFAIK, The pointerMove event is being used to keep track of the hoverValue, and then the click event is used to save the hoverValue.

The following series of events can be used to simulate the rating change using the react testing library.

...
const ratingStar = getByRole('rating').querySelector('.star-svg');
const ratingContainer = getByRole('rating').querySelector('.react-simple-star-rating'),

fireEvent.pointerMove(ratingStar);
fireEvent.click(ratingContainer);
...
<div role="rating">
    <Rating onClick={...} />
</div>

@davidnewcomb
Copy link

davidnewcomb commented Jun 10, 2024

I don't think @Parassharmaa's code works, or at least I couldn't make it work with cypress.
The problem here is that cypress uses CSS selectors to access things to click on or move over.

So from:

<div data-testid="ratings">
  <Rating onClick={starPickerChange} initialValue={sco} readonly={!loggedin} iconsCount={10} size={24} />
</div>

you actually get the following in the DOM. Each svg has a path but I've excluded them here for brevity.

<div data-testid="ratings">
   <span class="style-module_starRatingWrap__q-lJC" style="direction:ltr">
      <span class="style-module_simpleStarRating__nWUxf react-simple-star-rating" style="cursor:pointer" aria-hidden="true">
         <span class="style-module_emptyIcons__Bg-FZ empty-icons" style="color:#cccccc">
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
         </span>
         <span class="style-module_fillIcons__6---A filled-icons" style="left: 0px; color: rgb(255, 188, 11); width: 10%;" title="1 out of 10">
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
            <svg class="star-svg" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"/>
         </span>
      </span>
   </span>
</div>

For an annoying moment you can see both sets of stars at the same time, 10 empty next to 10 full, then it snaps to the correct number of filled stars. You have to add a time delay to wait for that to finish before you can move, that's the first problem #49.

Second problem is that you don't know which star out of the 10-on and 10-off you need to pick and if you pick the wrong one then it hangs the testing framework until it times out.

So for clarity, doing this will only work if the number 8 is not selected.

cy.get('.react-simple-star-rating .empty-icons :nth-child(8)').trigger('pointermove')

and doing this will only work if number 8 is already selected:

cy.get('.react-simple-star-rating .filled-icons :nth-child(8)').trigger('pointermove')

I thought I could just use a selector that will trigger on both, but it obviously doesn't make any sense to have a mouse move on 2 elements at the same time, so cypress complained about that.

Hopefully you can see the problem now. filled-icons and empty-icons exist in the DOM with no indication which is selected.

I've been trying to get this working for most of the afternoon and I'm stuck, so I'm going to add something to the onClick to update a hidden input and read it from there instead. But even then I have to calculate if the star is on or off depending on the number and pick the right class name for the selector. This is way more complicated than it needs to be!!!

Or does anyone know a better way? Or even a way to know how many stars are selected? Help!!

UPDATE:
I've finished writing my tests, or at least I can't spent anymore time on it and my solution is acceptable.

In the end I did:

cy.get('.react-simple-star-rating .empty-icons :nth-child(8)').trigger('pointermove')
cy.get('.react-simple-star-rating').click()
cy.get('.react-simple-star-rating .filled-icons :nth-child(3)').trigger('pointermove')
cy.get('.react-simple-star-rating').click()

because I know that is what it should be, but I have no way in html/css to see that the click was successful. Now the problem is that it gets 8 correct but it thinks I'm clicking number 1 the second time, probably related to the calculated position, just a guess.

@m-lyon
Copy link

m-lyon commented Jul 2, 2024

I came across this issue and @davidnewcomb solution did not work for me. For context i'm using react-testing-library with vitest and happy-dom.

After looking at the source code I narrowed it down to two problems:

  1. the handlePointerMove function within Rating was making a getBoundingClientRect() call:
    const { left, right, width } = currentTarget.children[0].getBoundingClientRect();
    which returned 0s in my testing suite for those attributes. To fix this i mocked it within my test via window.HTMLElement.prototype.getBoundingClientRect = () => ({ width: 100, left: 0, right: 100 }) as DOMRect.
  2. I needed to ensure a clientX value was provided in the pointer move event, like so: await userEvent.pointer({ target: svgStar, coords: { clientX: 30 } }).

With both of these changes I could effectively mock the user interaction via a pointer move event and a subsequent click on the parent container.

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

No branches or pull requests

4 participants