awran5 / react-simple-star-rating

A simple react component for adding a star rating to your project.
https://react-simple-star-rating.vercel.app/
MIT License
132 stars 30 forks source link

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

Open muhammadali-HazelSoft opened 6 months ago

muhammadali-HazelSoft commented 6 months ago

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 commented 6 months ago

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 commented 4 weeks ago

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 commented 6 days ago

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.