How to test a situation where user selects option from data list? I am asserting onChange
callback on the input using @testing-library/user-event
.
This is my React component:
// MyInput.js
import React from "react";
const MyInput = ({ value, list, id, onChange }) => {
return (
<label>
<strong>{id}</strong>
<div>
<input type="text" value={value} onChange={onChange} list={id} />
<datalist id={id} aria-label="datalist-items">
{list.map((item) => (
<option
key={`item-${item.id}`}
aria-label="data-list-item"
value={item.value}
/>
))}
</datalist>
</div>
</label>
);
};
export default MyInput;
This is my failing test
// MyInput.spec.js
import React from "react";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import MyInput from "./MyInput";
import data from "./data.json";
describe("MyInput", () => {
it("should trigger onChange with selected option", () => {
const onChange = jest.fn();
const list = [...data.list];
const screen = render(<MyInput onChange={onChange} list={list} />);
userEvent.selectOptions(screen.getByLabelText("datalist-items"), "first");
expect(onChange).toHaveBeenCalledWith("first");
});
});
Data provided to component:
// data.json
{
"list": [
{ "id": 1, "value": "first" },
{ "id": 2, "value": "second" }
]
}
However that does not work. Test reports failure:
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: "first"
Number of calls: 0
14 | userEvent.selectOptions(screen.getByLabelText("datalist-items"), "first");
> 16 | expect(onChange).toHaveBeenCalledWith("first");
17 | });
18 | });
You can find a live example in this CodeSandbox.
I want to simulate real-life scenario where user clicks into an input, the data list is rendered and user clicks one of the options. Should I be somehow targeting the rendered datalist instead? And how?
The onChange
event handler is used by <input type='text'>
jsx, so you should use type(element, text, [options]) to writes text inside an <input>
or a <textarea>
. selectOptions(element, values) is used for selecting the specified option(s) of a <select>
or a <select multiple>
element.
MyInput.jsx
:
import React from 'react';
const MyInput = ({ value, list, id, onChange }) => {
return (
<label>
<strong>{id}</strong>
<div>
<input type="text" value={value} onChange={onChange} data-testid="test" list={id} />
<datalist id={id} aria-label="datalist-items">
{list.map((item) => (
<option key={`item-${item.id}`} aria-label="data-list-item" value={item.value} />
))}
</datalist>
</div>
</label>
);
};
export default MyInput;
MyInput.spec.jsx
:
import React from 'react';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MyInput from './MyInput';
import data from './data.json';
describe('MyInput', () => {
it('should trigger onChange with selected option', () => {
expect.assertions(6);
let events = [];
const onChange = jest.fn().mockImplementation((e) => {
e.persist();
events.push(e);
});
const list = [...data.list];
const screen = render(<MyInput onChange={onChange} list={list} id="test" />);
userEvent.type(screen.getByTestId('test'), 'first');
expect(onChange).toBeCalledTimes(5);
events.forEach((e) => {
expect(onChange).toHaveBeenCalledWith(e);
});
});
});
test result:
PASS examples/65687415/MyInput.spec.jsx
MyInput
✓ should trigger onChange with selected option (44 ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
MyInput.jsx | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.175 s
package versions:
"react": "^16.14.0",
"@testing-library/react": "^11.2.2",
"@testing-library/user-event": "^12.6.0",