React-select: autoFocus prop and react-testing-library

Created on 27 Jun 2019  ยท  8Comments  ยท  Source: JedWatson/react-select

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.

Most helpful comment

@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();
    });
  });

All 8 comments

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

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

@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 ๐Ÿ˜ข

@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.

@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();
    });
  });

I'm glad to help you! ๐Ÿ˜„

I think I can close this now.

Thanks!

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

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

Was this page helpful?
0 / 5 - 0 ratings