Trying to test FormControlLabel in enzyme won't trigger click when simulate - reactjs

I am trying to test a FormControlLabel component from material UI.
When I try to simulate click Enzyme won't fire a click event on my component.
I tried using shallow, createShallow, mount, createMount.
In debug I get the component from the find and findWhere query and by the looks of it it has a props of control that contains the wanted checkbox.
I also tried to wrap the checkbox from the retrieved props with shallow and mount and it didn't work...
//parent.jsx
export public Parent = () => {
const [selected, setSelected] = useState(false);
const handleChange = (e,s) => {setSelected(s);};
...
return (
...
<FormControlLabel control={
<Checkbox onChange={handleChange}
checked={selected}}
label='some label'/>
...
);
}
//test.js
...
let component = createMount()(<Parent/>);
let checkbox = component.find(FormControlLabel)
.findWhere(c=>c.prop('label')==='some label').first();
checkbox.simulate('click');
checkbox.simulate('change');
//rest of the test
//the function handleChange is not called in debug.
Expected:
simulate click or change should call the onChange function
Actual:
change won't be triggered
the code works, the test dont.
please help!!!

Try this
let checkbox = component.find(FormControlLabel);
checkbox.prop('control').props.onChange({ target: { checked: true }, persist: jest.fn() });
Worked for me using shallow.

The trick is to construct an event that has a checked : true field.
modifying your code:
let checkbox = component.find(FormControlLabel).findWhere(c=>c.prop('label')==='some label').first();
// Wrap the control prop.
enzyme.shallow(checkbox.prop('control')).simulate('change', { target : { checked : true }} );

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

How can you prevent propagation of events from a MUI (v4) Slider Component

I have a MUI v4 Slider (specifically used as a range slider: https://v4.mui.com/components/slider/#range-slider) component inside an expandable component in a form, however, the onChange handler for the Slider component immediately propagates up into the parent and triggers the onClick handler which controls the hide/show.
In the child:
import { Slider } from '#material-ui/core';
export const MySliderComponent = ({ setSliderValue }) => {
let onChange = (e, value) => {
e.stopPropagation();
setSliderValue(value);
}
return <Slider onChange={onChange} />
}
In the parent:
let [expanded, setExpanded] = useState(false);
let toggle = (e) => setExpanded(!expanded);
return (
<React.Fragment>
<div className={'control'} onClick={toggle}>Label Text</div>
<div hidden={!expanded}>
<MySliderComponent />
</div>
</React.Fragment>
);
Points:
when I click inside the slider component, but not on the slider control, it does not trigger the toggle in the parent
when I click on the slider control, the event immediately (on mouse down) triggers the toggle on the parent
throwing a e.preventDefault() in the onChange handler has no effect
using Material UI v4 (no I can't migrate to 5)
I don't understand why the onChange would trigger the parent's onClick. How do I prevent this, or otherwise include a Slider in expandable content at all?
Edit:
After further debugging I found that if I removed the call to setSliderValue, that the parent did not collapse/hide the expanded content. Then I checked the state of expanded and it seems to be resetting without a call to setExpanded. So it looks like the parent component is re-rendering, and wiping out the state of the useState hook each time.
Following solution worked for me in a simmilar problem:
Give your parent component a unique id (for readability)
div id="parent" className={'control'} onClick={toggle}
Modify the parent's onClick handler (toggle):
let toggle = (e) => {
if (wasParentCLicked()) setExpanded(!expanded);
function wasParentCLicked() {
try {
if (e.target.id === "parent") return true;
} catch(error) {
}
return false;
}
}
For further help refer to the official documentation of the Event.target API: https://developer.mozilla.org/en-US/docs/Web/API/Event/target

Check that button is disabled in react-testing-library

I have a React component that generates a button whose content contains a <span> element like this one:
function Click(props) {
return (
<button disable={props.disable}>
<span>Click me</span>
</button>
);
}
I want to test the logic of this component with the use of react-testing-library and mocha + chai.
The problem at which I stuck at the moment is that the getByText("Click me") selector returns the <span> DOM node, but for the tests, I need to check the disable attribute of the <button> node. What is the best practice for handling such test cases? I see a couple of solutions, but all of them sound a little bit off:
Use data-test-id for <button> element
Select one of the ancestors of the <Click /> component and then select the button within(...) this scope
Click on the selected element with fireEvent and check that nothing has happened
Can you suggest a better approach?
Assert if button is disabled
You can use the toHaveAttribute and closest to test it.
import { render } from '#testing-library/react';
const { getByText } = render(Click);
expect(getByText(/Click me/i).closest('button')).toHaveAttribute('disabled');
or toBeDisabled
expect(getByText(/Click me/i).closest('button')).toBeDisabled();
Assert if button is enabled
To check if the button is enabled, use not as follows
expect(getByText(/Click me/i).closest('button')).not.toBeDisabled();
You can use toBeDisabled() from #testing-library/jest-dom, it is a custom jest matcher to test the state of the DOM:
https://github.com/testing-library/jest-dom
Example:
<button>Submit</button>
expect(getByText(/submit/i)).toBeDisabled()
For someone who is looking for the test in which the button is not disabled.
import { render } from '#testing-library/react';
const { getByText } = render(Click);
expect(getByText(/Click me/i).getAttribute("disabled")).toBe(null)
I would politely argue you are testing an implementation detail, which react-testing-library discourages.
The more your tests resemble the way your software is used, the more confidence they can give you.
If a button is disabled, a user doesn't see a disabled prop, instead they see nothing happen. If a button is enabled, a user doesn't see the omission of a disabled prop, instead they see something happen.
I believe you should be testing for this instead:
const Button = (props) => (
<button
type="submit"
onClick={props.onClick}
disabled={props.disabled}
>
Click me
</button>
);
describe('Button', () => {
it('will call onClick when enabled', () => {
const onClick = jest.fn();
render(<Button onClick={onClick} disabled={false} />);
userEvent.click(getByRole('button', /click me/i));
expect(onClick).toHaveBeenCalledTimes(1);
});
it('will not call onClick when disabled', () => {
const onClick = jest.fn();
render(<Button onClick={onClick} disabled={true} />);
userEvent.click(getByRole('button', /click me/i));
expect(onClick).not.toHaveBeenCalled();
});
})
toHaveAttribute is good option in using attribute.
<button data-testid="ok-button" type="submit" disabled>ok</button>
const button = getByTestId('ok-button')
//const button = getByRole('button');
expect(button).toHaveAttribute('disabled')
expect(button).toHaveAttribute('type', 'submit')
expect(button).not.toHaveAttribute('type', 'button')
expect(button).toHaveAttribute('type', expect.stringContaining('sub'))
expect(button).toHaveAttribute('type', expect.not.stringContaining('but'))
Hope this will be helpful.
You can test the disable prop of the button just by using #testing-library/react as follows.
example:
import { render } from '#testing-library/react';
const {getByText} = render(<Click/>)
expect(getByText('Click me').closest('button').disabled).toBeTruthy()
Another way to fix this would be to grab by the role and check the innerHTML like,
const { getByRole } = render(<Click />)
const button = getByRole('button')
// will make sure the 'Click me' text is in there somewhere
expect(button.innerHTML).toMatch(/Click me/))
This isn't the best solution for your specific case, but it's one to keep in your back pocket if you have to deal with a button component that's not an actual button, e.g.,
<div role="button"><span>Click Me</span></div>
My solution, It seems to me that this case covers well what is necessary. Check that the button is disabled, so toHaveBeenCalledTimes must receive 0
test('Will not call onClick when disabled', () => {
const mockHandler = jest.fn()
render(<Button title="Disabled account" disabled={true} onClick={mockHandler} />)
const button = screen.getByText("Disabled account")
fireEvent.click(button)
expect(mockHandler).toHaveBeenCalledTimes(0)
expect(button).toHaveProperty('disabled', true)
})

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.

how to use semantic UI React Checkbox toggle?

I am trying to use Semantic UI react for layout. These are the following sample code I am having trouble with onChange. I can check to click the toggle but resets everytime I refresh.
import {
Checkbox
} from 'semantic-ui-react'
onChangeInput(event) {
let name = event.target.name
let value = event.target.value
let talent = this.state.newTalentProfile
talent[name] = value
this.setState({
newTalentProfile: talent
})
}
<Select
name = "willing_to_relocate"
ref = "willing_to_relocate"
defaultValue = {this.props.talent.willing_to_relocate}
onChange = { this.onChangeInput.bind(this)} >
<Option value = ""label = "" / >
<Option value = "YES"label = "YES" / >
<Option value = "NO"label = "NO" / >
</Select>
the below code doesn't work, but the above one works when i make changes it saves it to database
<Checkbox toggle
name = "willing"
ref = "willing"
label = "Willin To Relocate"
onChange = {this.onChangeInput.bind(this)
}
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
onChangeCheckbox = (evt, data) => {
let checked = data.checked
console.log(checked)
}
<Checkbox toggle label='Running discounted price?'
onClick={(evt, data)=>this.onChangeCheckbox(evt, data)}
/>
This should do the trick.
Your code suggests you expect event.target to be a checkbox element, but it is not. If you examine the variable event.target within your browser, you'll notice that it points to a label element, not the checkbox itself. Therefore, I'm guessing that your event.target.value and event.target.label are evaluating to null, causing the rest of your code to not function the way you expect.
There's many ways to get around this. One way is to set a state variable for your component to represent the state of the checkbox:
class TheComponentThatContainsMyCheckbox extends Component {
state = {
textValue: null,
willingToRelocate: true
}
...
Then, you can create a handler that toggles this state when checked:
toggleCheckBox = () => {
const willingToRelocate = !(this.state.willingToRelocate);
this.setState({willingToRelocate});
// Note: if you have other logic here that depends on your newly updated state, you should add in a callback function to setState, since setState is asynchronous
}
Notice that I'm using arrow syntax here, which will automatically bind this for me. Then, hook it up to the onChange event of the Semantic UI Checkbox:
<Checkbox label='Willing to Relocate.' onChange={this.toggleCheckBox}/>
You can now use your this.willingToRelocate state to decide other application logic, passing it up or down using whatever state management pattern you like (state container/store like Redux, React Context, etc.).
I assume your this.onChangeInput handles database updates.
semantic ui react offers a prop checked for Checkbox.
You should have a state {checked: false} in your code, then this.setState whatever in componentDidMount after retrieving the database record.
Pass this.state.checked into checked prop of Checkbox.

Resources