Ashwini
Ashwini

Reputation: 391

onChange handler in async typeahead is not called when triggered using "user event" in react testing library

I am trying to test a specific functionality that's defined inside onChange handler of AsyncTypeahead component from react-bootstrap-typeahead library. But onChange handler and selected property are not triggered at all. They are triggered when I type something on UI and select one option from typeahead dropdown. But from tests, both onChange and selected are not triggered. Here's the code for your reference:

Search.jsx

const getSelectedObject = value => {
        if (value) {
            return {name: value};
        }
        return value;
    };
const Search = props => {
    const client = useApolloClient();
    const [options, setOptions] = useState([]);
    const [isLoading, setIsLoading] = useState(false);

    const onSearch = async searchInput => {
        props.clear();
        if (!searchInput) return;
        setIsLoading(true);
        try {
            const variables = {
                fields: props.fields,
                value: searchInput,
            };
            const res = await client.query({
                query: props.query,
                variables,
            });
            const newOptions = res ? res.data: [];
            setOptions(newOptions);
        } catch (err) {
            console.error(err);
        } finally {
            setIsLoading(false);
        }
    };

    const searchValue = debounce(onSearch, 2000);
    return (
        <th style={props.style || {minWidth: "100px"}} id={props.id}>
            <label style={{visibility: "hidden"}} htmlFor={`${props.id}_input`}>
                {props.id}
            </label>
            <AsyncTypeahead
                inputProps={{name: props.name, id: `${props.id}_input`}}
                labelKey="name"
                id="fields"
                placeholder={props.placeholder || "Search"}
                onChange={(value) => console.log("onchange trigger", value)}
                selected={(val) => getSelectedObject(val)}
                isLoading={isLoading}
                onSearch={searchValue}
                options={options}
            />
        </th>
    );
};

Search.propTypes = {
    id: PropTypes.string.isRequired,
    name: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired,
    selected: PropTypes.object,
    placeholder: PropTypes.string,
    accountRole: PropTypes.string.isRequired,
    fieldNames: PropTypes.array.isRequired,
};

Search.defaultProps = {
    selected: undefined,
    placeholder: "Search",
};

export default Search;

Search.test.jsx

test.only("selecting an option from search result SHOULD call ONCHANGE handler", async () => {
                render(<ConversationSearch {...props} />);

        // When
        userEvent.type(screen.getByRole("combobox"), "to");

        // Then
        expect(screen.getByRole("combobox").getAttribute("value")).toBe("to");
        await waitFor(() =>
            expect(screen.getByRole("listbox")).toBeInTheDocument(),
        );
        expect(
            screen.getByRole("link", {name: "Type to search..."}),
        ).toBeDefined();
        await waitFor(() =>
            expect(
                screen.getByRole("link", {name: "Searching..."}),
            ).toBeInTheDocument(),
        );
        await waitFor(() =>
            expect(
                screen.getByRole("option", {name: "option1"}),
            ).toBeInTheDocument(),
        );
        expect(
            screen.getByRole("option", {name: "option2"}),
        ).toBeInTheDocument();

        expect(screen.getByLabelText("option1")).toBeVisible();

                fireEvent.change(screen.getByRole("combobox"), {target: {value: "option1"}});

       // expect(screen.getByRole("combobox").getAttribute("value")).toBe("option1");
    });

I have modified the parts of the code to keep the focus more on component rather than business context.

Upvotes: 2

Views: 885

Answers (1)

ericgio
ericgio

Reputation: 3509

It's a little unclear what functionality you're trying to test, but it sounds like you just want to assert that onChange is called when an option is selected. Note that the library already tests this, but if you insist on confirming that, go ahead.

Based on the code in Search.jsx, it doesn't look like onChange is being passed on to the typeahead, so be sure to do that. Something like this should work (I haven't actually tested):

test.only("selecting an option from search result SHOULD call ONCHANGE handler", async () => {
  // Pass a spy to the `onChange` handler.
  const onChange = jest.fn();
  render(<ConversationSearch {...props} onChange={onChange} />);

  userEvent.type(screen.getByRole("combobox"), "to");

  ...

  const option = await screen.findByRole("option", { name: "option1" });

  userEvent.click(option);

  // `onChange` should be called after a selection.
  await waitFor(() => {
    expect(onChange).toHaveBeenCalledTimes(1);
  });
});

Upvotes: 1

Related Questions