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

autoFocus prop and react-testing-library #3655

Closed
montezume opened this issue Jun 27, 2019 · 8 comments
Closed

autoFocus prop and react-testing-library #3655

montezume opened this issue Jun 27, 2019 · 8 comments

Comments

@montezume
Copy link

Summary

When testing react-select inputs with @testing-library/react, it's not possible to programatically open the select. This is with the latest version of react-select.

Reproduction

I created a reproduction repo. I pasted the relevant code below to make it easier to follow

//App.js
import React, { Component } from "react";
import Select from "react-select";
import "./App.css";

const fruits = [
  { label: "Papaya", value: "papaya" },
  { label: "Apple", value: "apple" }
];

class App extends Component {
  render() {
    return (
      <div className="App">
        <label htmlFor="selectInput">Select a fruit</label>

        <Select
          autoFocus={this.props.autoFocus}
          id="selectInput"
          options={fruits}
        />
      </div>
    );
  }
}

App.defaultProps = {
  autoFocus: false
};

export default App;
// App.test.js

import React from "react";
import { render, fireEvent } from "@testing-library/react";
import App from "./App";

describe("select input", () => {
  describe("without autofocus", () => {
    it("should be able to open", () => {
      const { getByLabelText, getByText, debug } = render(<App />);

      const input = getByLabelText("Select a fruit");
      input.focus();
      fireEvent.keyDown(input, { key: "ArrowDown" });
      expect(getByText("Papaya")).toBeInTheDocument();
    });
  });
  describe("with autofocus", () => {
    it("should be able to open", () => {
      const { getByLabelText, getByText, debug } = render(
        <App autoFocus={true} />
      );

      const input = getByLabelText("Select a fruit");
      input.focus();
      fireEvent.keyDown(input, { key: "ArrowDown" });
      expect(getByText("Papaya")).toBeInTheDocument();
    });
  });
});

When I run the test, this is the result

 FAIL  src/App.test.js
  select input
    without autofocus
      ✓ should be able to open (135ms)
    with autofocus
      ✕ should be able to open (59ms)

  ● select input › with autofocus › should be able to open

    Unable to find an element with the text: Papaya. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body>
      <div>
        <div
          class="App"
        >
          <label
            for="selectInput"
          >
            Select a fruit
          </label>
          <div
            class=" css-2b097c-container"
            id="selectInput"
          >
            <span
              aria-live="polite"
              class="css-1laao21-a11yText"
            >
              <p
                id="aria-selection-event"
              >


              </p>
              <p
                id="aria-context"
              >

                  0 results available. Select is focused ,type to refine list, press Down to open the menu,
              </p>
            </span>
            <div
              class=" css-1pahdxg-control"
            >
              <div
                class=" css-1hwfws3"
              >
                <div
                  class=" css-1wa3eu0-placeholder"
                >
                  Select...
                </div>
                <div
                  class="css-1g6gooi"
                >
                  <div
                    class=""
                    style="display: inline-block;"
                  >
                    <input
                      aria-autocomplete="list"
                      autocapitalize="none"
                      autocomplete="off"
                      autocorrect="off"
                      id="react-select-3-input"
                      spellcheck="false"
                      style="box-sizing: content-box; width: 2px; border: 0px; font-size: inherit; opacity: 1; outline: 0; padding: 0px;"
                      tabindex="0"
                      type="text"
                      value=""
                    />
                    <div
                      style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: inherit; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
                    />
                  </div>
                </div>
              </div>
              <div
                class=" css-1wy0on6"
              >
                <span
                  class=" css-1okebmr-indicatorSeparator"
                />
                <div
                  aria-hidden="true"
                  class=" css-1gtu0rj-indicatorContainer"
                >
                  <svg
                    aria-hidden="true"
                    class="css-19bqh2r"
                    focusable="false"
                    height="20"
                    viewBox="0 0 20 20"
                    width="20"
                  >
                    <path
                      d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
                    />
                  </svg>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </body>

      23 |       input.focus();
      24 |       fireEvent.keyDown(input, { key: "ArrowDown" });
    > 25 |       expect(getByText("Papaya")).toBeInTheDocument();
         |              ^
      26 |     });
      27 |   });
      28 | });

      at getElementError (node_modules/@testing-library/dom/dist/query-helpers.js:46:10)
      at args (node_modules/@testing-library/dom/dist/query-helpers.js:100:13)
      at args (node_modules/@testing-library/dom/dist/query-helpers.js:83:17)
      at Object.getByText (src/App.test.js:25:14)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        1.91s
Ran all test suites.
@iamchanii
Copy link

iamchanii commented Jul 30, 2019

I have same issue. tried to use fireEvent.mouseDown() or userEvent.click(). any solutions there? and I didn't use autoFocus prop.

@iamchanii
Copy link

iamchanii commented Jul 31, 2019

Finally, I resolve this issue. there is two points: 1. You should simulate keydown event for display dropdown. 2. Dropdown renders menu asynchronously.

it('should display dropdown menu and call onSortChange when option clicked', async () => {
    const { getByText, onSortChange, debug } = setup();

    // (1) I tried to simulate mouse event but not working. this only is effective solution.
    fireEvent.keyDown(document.querySelector('.list__control'), { key: 'ArrowDown', keyCode: 40 });

    // (2) Dropdown renders menu asynchronosly. you have to wait for element to find by text
    await waitForElement(() => getByText('Option 1'));
    fireEvent.click(getByText('Option 1'));

    expect(onSortChange).toHaveBeenCalledTimes(1);
    expect(onSortChange).toHaveBeenLastCalledWith(['code', 'ASC']);
});

And don't forget use classNamePrefix props for write test easy.

    <ReactSelect
        classNamePrefix="list"
        noOptionsMessage={noOptionsMessage}
        isSearchable={false}
        isClearable={false}
        components={selectComponents}
        {...props}
    />

image

Ref: https://stackoverflow.com/a/56183912

@montezume
Copy link
Author

@iamchanii are you using the autoFocus prop? I tried your solution and wasn't able to get it to work when it's set to true. Without that prop I can interact with ReactSelect with RTL with no issues 😢

@iamchanii
Copy link

@montezume No, but I wish it is help for you.

image
image

as you can see, I added fireEvent.blur() line before simulate keydown and it's work. but I don't think it is not RTL spirit:

The more your tests resemble the way your software is used, the more confidence they can give you.

@montezume
Copy link
Author

montezume commented Aug 5, 2019

@iamchanii thanks for your help!

I figured out the source of the problem.

When I did

      const input = getByLabelText('Select a fruit');
      fireEvent.blur(input);

the input that I was grabbing was not actually an input. It's a div.

Screen Shot 2019-08-05 at 11 08 46 AM

So calling blur on that div doesn't do what we need.

So instead, I do

  describe("with autofocus", () => {
    it.only("should be able to open", async () => {
      const { getByLabelText, getByText, debug } = render(
        <App autoFocus={true} />
      );

      const input = getByLabelText("Select a fruit");

      fireEvent.blur(document.querySelector("input"));

      fireEvent.keyDown(input, {
        key: "ArrowDown",
        keyCode: 40
      });

      await waitForElement(() => getByText("Papaya"));
      fireEvent.click(getByText("Papaya"));
      expect(getByText("Papaya")).toBeInTheDocument();
    });
  });

and it works, because now the blur is being called on the actual input.

Thanks for your help! 🎉

I was even able to remove the async

  describe("with autofocus", () => {
    it.only("should be able to open", () => {
      const { getByLabelText, getByText } = render(<App autoFocus={true} />);

      const containerDiv = getByLabelText("Select a fruit");
      const input = containerDiv.querySelector("input");

      fireEvent.blur(input);

      fireEvent.keyDown(input, {
        key: "ArrowDown",
        keyCode: 40
      });

      expect(getByText("Papaya")).toBeInTheDocument();
      fireEvent.click(getByText("Papaya"));
      expect(getByText("Papaya")).toBeInTheDocument();
    });
  });

@iamchanii
Copy link

I'm glad to help you! 😄

@montezume
Copy link
Author

I think I can close this now.

Thanks!

@prayoosh-rawane
Copy link

I was facing the same issue with autoFocus props in unit test cases.

When used fireEvent.blur(input); all test cases were working.

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

3 participants