Testing Semantic-UI Dropdown: How to wait for it to be populated? - reactjs

I've successfully implemented the Semantic-UI Dropdown to display a list of companies. Now I'm trying to build Jest / React Testing Library tests for it. To accomplish this, I built this Mock Request:
beforeEach(() => {
request.get = jest.fn(() => Promise.resolve({
companies: [
{"company_id": 1, "company_name": "ABC"},
{"company_id": 2, "company_name": "DEF"}
]
}));
});
Based on a console.log I added to my component code, this appears to work as expected.
Here's an abridged example of my instance of this element:
<div id="companyId" data-testid="companies-dropdown" role="combobox">
<input autocomplete="companyId" class="search" type="text">
<div class="default text" "role="alert">Ex. ABC Corp</div>
<div role="listbox">
<div role="option"><span class="text">ABC</span></div>
<div role="option"><span class="text">DEF</span></div>
</div>
</div>
Where I'm struggling is to correctly wait in my test for the Dropdown to get populated:
it('Should populate the Search and Select Company dropdown with 2 companies', async () => {
const { getByTestId, getByText, getByRole } = displayModal();
const listBox = await waitForElement(() => getByRole('listbox'));
});
The error message is: Unable to find an accessible element with the role "listbox"
I also tried this alternate approach but got the same error message:
const listBox = await waitForElement(() => within(getByTestId('companies-dropdown')).getByRole('listbox'));
Might anyone have any ideas what I'm doing wrong?

I happened to see this while I was searching something else. But in your case, you are using wrong role. I achieved my use case with the following:
First focus the input element of the div to open dropdown. I use the input element:
userEvent.click(getByLabelText('Company'))
The above can be replaced by using data-testid or with role 'combobox'
Get all the roles for the select options:
getAllByRole('option')
const companyAbc = getAllByRole('option').find(ele => ele.textContent === 'ABC') userEvent.click(companyAbc); // verify your onChange event
In your async case:
const companyAbc = await findAllByRole('option').find(ele => ele.textContent === 'ABC') userEvent.click(companyAbc); // verify your onChange event
SemanticReact also has onSearchChange event that you can use to trigger for every input search if that's the use case.

Related

React testing library - how to test setstate incustom onchange method of child component which updates state of child in parent component

I am new to react testing. I have a child component (to which I have no edit access) which is a table along with search box option. It has a event onSearchChange which will update the state with text if value entered in search box. Then we have logic for search using that text. Both set state and search logic is in parent component on which child is rendered. Its working perfectly fine in real time, but in unit testing I am not sure why the onSearchChange is not updating the state(number of rows in table) when I change the value of search box.
This onSearchChange is of type
onSearchChange?: (e: React.SyntheticEvent, value: string) => void;
I have also tried firevents and userevent.type. But nothing works. I have code coverage problem because of this since I need to cover code of search logic.
Please help. Thank you !
Parent Component
const [searchText, setSearchText] = useState("");
//logic to perform search
return(<div className="patient-list">
<div className="table">
<SearchTable onSearchChange={(e) => {
setSearchText((e.target as HTMLInputElement).value)}} table={result} />
</div>
</div>
)
test file
it("should search the table", () => {
const { container } = render(<ParentComponent />);
let searchInputBox = screen.getByPlaceholderText ("Search the results") as HTMLInputElement
act(() => {
searchBoxInput.value = "Age";
ReactTestUtils.Simulate.change(searchBoxInput);
const rows = container.getElementsByClassName("class1")
expect(rows).toHaveLength(6);
});
});

Trouble on testing onChange property from ant design

I’m having trouble in testing the behaviour of an ant design component.
I use Text component from ant design with ‘editable’ and ‘onChange’ properties for editing a comment. For saving the new content I have to click anywhere on the page or to press enter and the onChange function will be triggered. I tested manually and everything works fine.
In tests, I manage to edit the content of the comment, but when I simulate the enter pressing (or the clicking on any DOM element) the onChange function is not called.
What should I do in order to trigger the onChange function?
Here's my component:
<Text editable={{ onChange: editComment }}
id={"edit-comment-" + props.commentIndex}>
{props.body}
</Text>
Here's my test:
the test includes both methods of triggering the onChange function, but I did not use both of them at the same time
test('Edit comment request', async () => {
const fakeResponse = {
success: 1
};
jest.spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve(fakeResponse)
})
);
const editButton = container.querySelector("span[aria-label='edit']");
await fireEvent.click(editButton);
// Edit the comment content
fireEvent.change(screen.getByRole('textbox'), { target: { value: "edited comment" } });
// Save the comment content by pressing enter
await fireEvent.keyPress(screen.queryByText(/edited comment/i),
{ key: "Enter", code: "Enter", keyCode:13, charCode: 13 });
// Save the comment content by clicking on a random DOM element
await fireEvent.click(container.querySelector('.ant-comment-content-author'));
await wait(() => expect(global.fetch).toHaveBeenCalled());
});
Try to set _defaultValue property before dispatch the onChange event:
const textbox = screen.getByRole('textbox');
textbox.value = "edited comment";
textbox._valueTracker.setValue(null);
fireEvent.change(textbox, {bubbles: true});
To React the value is still unchanged. Check this issue: https://github.com/facebook/react/issues/11488

Mocking a simple function in react.js, jest, enzyme

I am new to testing in react and am building a simple todo application. I am struggling to mock the delete function in the lists component.
My test looks like this at the moment
it('deletes an item from the list', () => {
const deleteItem = jest.fn();
const items = [{body: "New note", id: "12345"}]
const textEl = wrapper.find("#text-output p")
wrapper = shallow(<ListItems list={items} deleteItem={deleteItem}/>)
wrapper.find('#delete').simulate('click')
expect(textEl.text()).toContain("//Should be empty")
})
currently the when I run the tests the error reads..
Expected substring: "//Should be empty"
Received string: "New note X"
The application works fine, but I am not mocking the delete function correctly in my test as a "New Note" is still being passed through. What am I doing wrong?
just in case it helps, here is the file I am testing..
function ListItems(props) {
const list = props.list
const displayItems = list.map((item, index) =>
{
return <div id='text-output' key={index}>
<p>
{item.body }
<button
id="delete"
onClick={ () => props.deleteItem(item.id)}>
X
</button>
</p>
</div>
})
return(
<div>
<h1>This is the list</h1>
{displayItems}
</div>
)
}
any help would be great!
In your test since you mocked the deleteItem callback it doesn't actually delete an item from the items object passed to the list prop. Also, const textEl = wrapper.find("#text-output p") before wrapper = shallow(<ListItems list={items} deleteItem={deleteItem}/>) probably also won't work as expected.
You can however instead assert the deleteItem mock was called with the specified item id. TBH, that is what the test should be anyway since that component under test doesn't appear to have any logic other than rendering a list of items and attaching an onClick callback.
.toHaveBeenCalledWith
it('should invoke deleteItem callback on button click with item id', () => {
const deleteItemMock = jest.fn();
const items = [{body: "New note", id: "12345"}];
wrapper = shallow(<ListItems list={items} deleteItem={deleteItemMock}/>);
wrapper.find('#delete').simulate('click');
expect(deleteItemMock).toHaveBeenCalledWith("12345");
});

Best way to test input value in dom-testing-library or react-testing-library

What is the best way to test the value of an <input> element in dom-testing-library/react-testing-library?
The approach I've taken is to fetch the raw input element itself via the closest() method, which then gives me direct access to the value attribute:
const input = getByLabelText("Some Label")
expect(input.closest("input").value).toEqual("Some Value")
I was hoping that there was a way I could this without having to directly access HTML attributes. It didn't seem like it was in the spirit of the testing library. Perhaps something like the jest-dom toHaveTextContent matcher matcher:
const input = getByLabelText("Some Label")
expect(input).toHaveTextContent("Some Value")
UPDATE
Based on request in the comments, here is a code example showing a situation where I felt the need to test the value in the input box.
This is a simplified version of a modal component I built in my app. Like, extremely simplified. The whole idea here is that the modal opens up with the input pre-filled with some text, based on a string prop. The user can freely edit this input and submit it by pressing a button. But, if the user closes the modal and then reopens it, I would like to have the text reset to that original string prop. I wrote a test for it because a previous version of the modal DID NOT reset the input value.
I'm writing this in TypeScript so that the types of each prop are very clear.
interface Props {
onClose: () => void
isOpen: boolean
initialValue: string
}
export default function MyModal({ onClose, isOpen, initialValue }) {
const [inputValue, setInputValue] = useState(initialValue)
// useEffect does the reset!
useEffect(() => {
if (!isOpen) {
setNameInput(initialValue)
}
}, [isOpen, initialValue])
return (
<SomeExternalLibraryModal isOpen={isOpen} onClose={onClose}>
<form>
<input
value={inputValue}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setInputValue(e.target.value)
}
/>
<button onClick={onClose}>Cancel</button>
</form>
</SomeExternalLibraryModal>
)
}
You are right in being suspicious of your testing method in regards to how this testing library wants you to test. The simplest answer to this question would be to use the getByDisplayValue query. It will search for an input, textarea, or select that has the value you are attempting to find. For example, using your component as an example, if I was trying to verify that inputValue = 'test', I would search like
expect(screen.getByDisplayValue('test')).toBeInTheDocument();
That is all you need to do. I assume your test is only rendering the MyModal component. Even if you have multiple inputs, it doesn't matter in regards to testing philosophy. As long as the getByDisplayValue finds any input with that value, it is a successful test. If you have multiple inputs and want to test that the exact input has the value, you could then dig into the element to determine it is the correct input:
note: you will need jest-dom for this to work.
expect(screen.getByDisplayValue('test')).toHaveAttribute('id', 'the-id');
or (without jest-dom):
expect(screen.getByDisplayValue('test').id).toBe('the-id');
You can of course search for any attribute you like.
One final alternative for testing the value is to find the input by role. This won't work in your example's case unless you add a label and affiliate it to your input through the htmlFor attribute. You could then test it like such:
expect(screen.getByRole('input', { name: 'the-inputs-id' })).toHaveValue('test');
or (without jest-dom):
expect(screen.getByRole('input', { name: 'the-inputs-id' }).value).toBe('test');
This I believe is the best way to test for the value while making sure the correct input has the value. I would suggest the getByRole method, but again, you will need to add a label to your example.
You can use screen.getByDisplayValue() to get the input element with a displayed value and compare it to your element.
type TestElement = Document | Element | Window | Node
function hasInputValue(e: TestElement, inputValue: string) {
return screen.getByDisplayValue(inputValue) === e
}
In your test:
const input = screen.getByLabelText("Some Label")
fireEvent.change(input, { target: { value: '123' } })
expect(hasInputValue(input, "123")).toBe(true)
expect(screen.getByLabelText("Name")).toHaveValue("hello"); - this gets you the value for the input :)
<label class="label" for="name">
Name
</label>
<div class="control ">
<input
class="input"
for="name"
id="name"
name="name"
value="hello"
/>
</div>
Test:
userEvent.type(screen.getByLabelText("Name"), "hello")
await waitFor(() => {
expect(screen.getByLabelText("Name")).toHaveValue("hello");
});
Using #testing-library/dom (or any of the wrapped libraries here)
You can do:
expect(inputField).toHaveDisplayValue('some input value');
Full example:
test('should show input with initial value set', async () => {
render(<Input type="text" value="John Doe" data-testid="form-field-firstname" />);
const inputField = await screen.findByTestId(`form-field-firstname`);
await waitFor(() => expect(inputField).toHaveDisplayValue('John Doe')));
});
There is very clean way to test it using testing library.
//In describe
const renderComponent = (searchInputValue, handleSearchInputValue) => {
const wrapper = render(<yourComponentWithInput
value={searchInputValue}
onChange={handleSearchInputValue}
/>);
return wrapper;
};
//In test
const mockHandleSearchInputValue = jest.fn();
const { getByLabelText } = renderComponent('g', mockHandleSearchInputValue);
const inputNode = getByLabelText('Search label'); // your input label
expect(inputNode.value).toBe('s'); // to test input value
fireEvent.change(inputNode, { target: { value: 'su' } }); // triggers onChange event
expect(mockHandleSearchInputValue).toBeCalledWith('su'); // tests if onChange handler is called with proper value

How to simulate selecting from dropdown in Jest / enzyme testing?

I'm trying to write jest tests for my React component that has a dropdown like this:
<select id="dropdown" onChange={this.handlechange} ref={this.refDropdown}>
{this.props.items.map(item => {
return (
<option key={item.id} value={item.id}>
{item.name}
</option>
);
})}
</select>
and the handler looks like this:
handlechange = () => {
const sel = this.refDropdown.current;
const value = sel.options[sel.selectedIndex].value;
//...
}
I want to simulate a user selecting the 2nd entry (or anything other than the first) in the list but am having trouble. If I simulate a "change" event it does fire the call to handlechange() but selectedIndex is always set to zero.
I tried this code in the jest test but it doesn't cause selectedIndex to be accessible in the handler.
const component = mount(<MyComponent/>);
component.find("#dropdown").simulate("change", {
target: { value: "item1", selectedIndex: 1 }
});
What happens is almost correct. If I look at the incoming event, I can see e.value is set to "item1" as I set, but it doesn't act like the selection was actually made.
I've also tried trying to send "click" simulations to the Option element directly but that does nothing.
What's the right way to simulate a selection from a dropdown?
Try this approach:
wrapper.find('option').at(0).instance().selected = false;
wrapper.find('option').at(1).instance().selected = true;
You can trigger a change event since you have your this.handlechange trigger onChange:
const component = mount(<MyComponent/>);
component.find('#dropdown').at(0).simulate('change', {
target: { value: 'item1', name: 'item1' }
});
I would say you have to add .at(0) because Enzyme will find a list of values even if you only have one element with that ID.
Try changing the html "onInput" to "onChange" because you are simulating the "change" event in jest.
Short Answer -
Use the following snippet
import userEvent from "#testing-library/user-event";
userEvent.selectOptions(screen.getByTestId("select-element-test-id"), ["option1"]);
Detailed Answer -
.tsx file
.
.
<Form.Select
aria-label="Select a value from the select dropdown"
required
onChange={(e) => {
console.log("Option selected from the dropdown list", e.target.value);
optionChangedHandler(e.target.value);
}}
data-testid="select-element-test-id"
>
...CODE FOR RENDERING THE LIST OF OPTIONS...
</Form.Select>
.
.
.test.tsx file
import userEvent from "#testing-library/user-event";
it("Check entire flow", async () => {
render(
<YourComponent/>
);
// CHECK IF SELECT DROPDOWN EXISTS
const selectDropdown = await waitFor(
() => screen.getByTestId("select-element-test-id"),
{
timeout: 3000,
}
);
expect(selectDropdown ).toBeInTheDocument();
//"option2" is the element in the select dropdown list
userEvent.selectOptions(screen.getByTestId("select-element-test-id"), [
"option2",
]);
}
The above code will trigger the onChange function of the select element.

Resources