I'm trying to attach a spy to a click event on my react component. I am using Enzyme with Mocha and Chai but I'm having trouble getting the following test to pass:
it('Handles a Click Event', () => {
let rootId = Bigtree['rootId'];
const spy = sinon.spy();
const render = shallow(<EnvH tree={smalltree.objects}
node={smalltree.objects[rootId]} root={rootId}/>);
render.find('.toggler').simulate('click');
expect(spy.called).to.be.true;
});
Here is the component I am testing:
class EnvH extends React.Component {
return (
<div className='top' key={Math.random()}>
<div className={'tableRow row'} id={node.id} style={rowStyle} key={Math.random()}>
<div style={nodeStyle} className={'root EnvHColumn ' + classNames(classObject)} key={Math.random()}>
//Here is the actual Click Item in question
<span className={'toggler'} onClick={this.toggleCollapseState.bind(this)}>{node.type} - {node.name}</span>
</div>
<ColumnContent columnType={'description'} nodeInfo={node.description} />
<ColumnContent columnType={'createdOn'} nodeInfo={node.createdAt} />
<ColumnContent columnType={'updatedOn'} nodeInfo={node.updatedAt} />
</div>
<div className={'Children'} style={childrenStyle} key={Math.random()}>
{childNodes}
</div>
</div>
);
Here is the function that gets called when the span gets clicked:
toggleCollapseState() {
this.setState({collapsed: !this.state.collapsed});
};
Thank you in advance for any help on this one. I should also mention that the test is not passing stating that it is expecting true but found false.
Your test is not passing because the spy function is not given to the span.toggler element, so it's never called by it.
To make the test pass you should instead write
sinon.spy(EnvH.prototype, 'toggleCollapseState')
and then
expect(EnvH.prototype.toggleCollapseState.calledOnce).to.be.true
Related
This question already has answers here:
When to use 'componentDidUpdate' method?
(9 answers)
Closed 2 years ago.
I am a complete newbie in the world of development. I am trying to make an app where we fetch movies. We can search and sort. I don't want to use any plugins as of now.
I have been successful in fetching the data and showing on the grid. Also, searching is working well. But I'm confused if I shall put the searching method in componentDidUpdate React lifecycle hook or not. If it shall be how to do it?
Following is my code:
class App extends Component
{
constructor(props)
{
super(props);
this.state = {
movies: [],
filteredMovies: []
}
}
componentDidMount()
{
axios.get('https://api.themoviedb.org/3/movie/top_rated?api_key={0}&language=en-US&page=1')
.then(response => {
this.setState({
movies : response.data.results,
filteredMovies : response.data.results
});
});
}
componentDidUpdate()
{
???
}
searchBy = (columnSearch, evt) =>
{
console.log(columnSearch);
let movie = this.state.movies;
if (evt!==undefined && evt.target.value !== undefined && evt.target.value.length > 0) {
movie = movie.filter(function(i) {
console.log(columnSearch);
return i[columnSearch].toString().match( evt.target.value );
});
}
this.setState({
filteredMovies: movie
});
}
render(){
const movieRows =( this.state.filteredMovies ===[] || this.state.filteredMovies === undefined)?[]:
this.state.filteredMovies.map( (rowData, index) => <Movie key ={index} {...rowData} />);
if(movieRows !== [] && movieRows !== undefined && movieRows !== null)
return (
<div className="table">
<div className="header row">
<div className="headercenter">Movie Id
<input type="text" onChange={(evt)=>this.searchBy('id',evt)}/>
</div>
<div className="headercenter"> Title <input type="text" onChange={(evt)=>this.searchBy('title',evt)}/></div>
{/* <div className="" onClick={() => this.sortBy('overview')}>OverView</div> */}
<div className="headercenter" >Popularity<input type="text" onChange={(evt)=>this.searchBy('popularity',evt)}/></div>
<div className="headercenter" >Rating<input type="text" onChange={(evt)=>this.searchBy('vote_average',evt)}/></div>
</div>
<div className="body">
{movieRows}
</div>
</div>
) ;
};
}
export default App;
const movie = (props) => (
<div className="row">
<div>{props.id}</div>
<div>{props.title}</div>
{/* <div>{props.overview}</div> */}
<div>{props.popularity}</div>
<div>{props.vote_average}</div>
</div>
);
export default movie;
ComponentDidUpdate called when any state update take places inside the component, so you can put search method inside ComponentDidUpdate or any method but normally we do things inside ComponentDidUpdate which do require some state change and also do not require user input. For any searching you must be requiring user input so I don't recommend that you should put search method inside ComponentDidUpdate instead put that method on onChange event handler.
Note: ComponentDidUpdate if not handled correctly i.e., you update any state inside this without any condition will cause Infinite Loop
IF you want to call your api in componentDidUpdate method. You should use if condition. Because when your dom is update this method will trigger.
It should look like something like this. I do not know your logic. Please change it for your use case.
componentDidUpdate(prevProps, prevState){
if(prevProps.movies.lenght != prevState.movies.lenght){
// call api
}
}
You don't need to put the searching function in componentDidUpdate, as it will be automatically called when the input field changes. The componentDidUpdate function will be called when the component rerenders (for example after state changes).
What you want, is to call the search function when the input in the input field changes, for which I would add keyup event like so:
<!DOCTYPE html>
<html>
<body>
<p>Write in the text field</p>
<input type="text" id="mySelect" />
<p>Down here will show what you write</p>
<p id="demo"></p>
<script>
document.getElementById('mySelect').addEventListener('keyup', search)
function search(event) {
console.log(event.target.value)
document.getElementById('demo').innerText = event.target.value
}
</script>
</body>
</html>
I have a controlled component with a form that consists of two radio buttons and a text input. I have a function that is called for onChange event of the text input and have written a test that fires a change event. I expect that the spy function should be called once but the test always fails.
Test
test('Update function is called when text entered into searchbox', () => {
const spy = jest.fn();
const {getByTestId} = render(<SearchDropdown handleChange={spy}/>);
expect(getByTestId('searchText').value).toBe("");
fireEvent.change(getByTestId('searchText'), { target: { value: "23" } });
expect(getByTestId('searchText').value).toBe("23");
expect(spy).toHaveBeenCalledTimes(1);
});
Component - does not take in any props
import React, { Component } from 'react'
import { faSearch } from '#fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import SearchResults from './SearchResults';
export default class SearchDropdown extends Component {
constructor(props){
super(props)
this.state = {
type: 'Spec',
number: '',
}
}
handleChange = (event) => {
let name = event.target.name;
let target = event.target.value;
this.setState({
[name]: target}
);
}
handleSearch = (event) => {
event.preventDefault();
if (this.state.number !== "") {
console.log("Make request to API");
}
}
render() {
return (
<div data-testid="searchMenu">
<div className="list-group dropdown-menu dropdown-menu-right mr-5 text-center">
<h5><FontAwesomeIcon icon={faSearch}/> Quick Search</h5>
<form className="m-2">
<div className="form-group" onChange={this.handleChange}>
<input type="radio" value="Spec" name="type" defaultChecked /> Specification
<input type="radio" value="Claim" name="type" className="ml-2"/> Claim
</div>
<div className="form-group">
<label>Search Number</label>
<input type="text" data-testid="searchText" onChange={this.handleChange} value={this.state.number} name="number" className="form-control"/>
</div>
<SearchResults />
</form>
<div className="panel panel-default">
</div>
<button className="bg-transparent border-0 link">Advanced Search</button>
</div>
</div>
)
}
}
Error Message
expect(jest.fn()).toHaveBeenCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
fireEvent.change(getByTestId('searchText'), { target: { value: "23" } });
expect(getByTestId('searchText').value).toBe("23");
expect(spy).toHaveBeenCalledTimes(1);
^
});
test('Clicking search brings up results modal', () => {
at Object.toHaveBeenCalledTimes (src/Tests/SearchDropdown.test.js:18:17)
Am I missing something obvious?
I'm fairly new to React and React Testing Library, but there are a couple of things I noted:
In your test you're passing a spy as the value for prop handleChange to your component, which it doesn't have; just as you state: "does not take in any props".
Your component defines handleChange function which you use an event handler, it doens't get it from a prop.
Your handleChange function should be passed only as prop to input elements, not a div.
You should have one handler per each input instead of calling the same handler for all of them.
That said, in order for your test to provide any value, you should change your SearchDropdown component so that it receives the handlers from a parent or you test that firing click/change events on the inputs actually updates the view as expected.
I don't think there's any value in testing if an internal event handler function has been called.
You're using React Testing Library in your test, but not in the way it's intended to.
From React Testing Library Intro:
The utilities this library provides facilitate querying the DOM in the
same way the user would. Finding for elements by their label text
(just like a user would), finding links and buttons from their text
(like a user would). It also exposes a recommended way to find
elements by a data-testid as an "escape hatch" for elements where the
text content and label do not make sense or is not practical.
Trying to test the following click event: Using Jest and Enzyme for ReactJS
<Modal isOpen={this.state.descriptionModalOpen} style={descriptionModalStyle}>
<div>
<div className='fullmodal'>
<div className="fullmodal_title">
<div className="fullmodal_title_add">Description</div>
</div>
<div className='sidemodal_addnew_x' id="close-Modal-id" onClick={this.closeModal}>
<FontAwesome name='xbutton' className='fa-times' />
</div>
</div>
{this.getDescription()}
</div>
</Modal>
Node cannot be found. The others click events test passed just fine, but this is the only one inside of a Modal.
Here is part of my test file
beforeEach(() => (wrapper = mount(<MemoryRouter keyLength={0}><Notifications {...baseProps} /></MemoryRouter>)));
it("should check button click events under Modal Component", () => {
baseProps.onClick.mockClear();
wrapper.find('Notifications').setState({
descriptionModalOpen: false,
});
wrapper.update()
wrapper.find('Notifications').find('#close-Modal-id').simulate("click");
});
did you try findWhere?
const yourElement = element.findWhere(node => node.id === "close-Modal-id")
yourElement.simulate('click');
If it doesn't, can you verify if findWhere traverses the node you're targeting?
I've been trying to test Callout component in my react project.
For simplification, following is React render component:
<div className="UserInfoDiv">
<div ref={this.menuButtonElement}>
<ActionButton id="toggleCallout"
onClick={changeIsCallOutVisibleProptoTrue}
text="Show Callout" />
</div>
<Callout
className="calloutClass1"
target={this.menuButtonElement.current}
hidden={!this.props.isCalloutVisible}>
<div id="callOutContainer">
<span>Need to test items here.<span>
<button className="clickForSomeAction">Simulate Click on this</button>
</div>
</Callout>
</div>
This works absolutely fine in UI. For testing in jest, I tried following:
userMenu = mount(<UserInfoDivComponent {...props} />);
UserInfoDiv.find("button#toggleCallout").simulate('click');
expect(changeIsCallOutVisibleProptoTrue.mock.calls.length).toBe(1);
userMenu.setProps({isCalloutVisible: true });
// Following only gives html(included UserInfoDiv,toggleCallout) `without html from callout`:
console.log(userMenu.html());
I need help on, How to test following scenarios?
Callout is Visible?
Find .clickForSomeAction button inside Callout.calloutClass1 and simulate click
There are similar component (ex: DropDown, Contextual Menu) from office-fabric-ui which renders HTML in document and not in current component HTML.
Finally, I did testing of Callout using ReactTestUtils as given in examples:
let component:any;
const container = document.createElement('div');
document.body.appendChild(container);
let threwException = false;
try {
component = ReactTestUtils.renderIntoDocument(<UserInfoDivComponent {...itemProps} />);
} catch (e) {
threwException = true;
}
expect(threwException).toEqual(false);
const UserInfoDiv= ReactTestUtils.findRenderedDOMComponentWithClass(component, "UserInfoDiv");
const clickForSomeAction = ReactTestUtils.findRenderedDOMComponentWithClass(component, "clickForSomeAction");
ReactTestUtils.Simulate.click(clickForSomeAction);
it works as expected, which few glitches as we can not query ReactTestUtils directly by querySelectors.
Edit 1
we can query using XML selectors.
So the main aim of me using refs is so that I can reset the scroll position of a scrollable div, this is an image of the div before adding content this is how it looks before dynamically adding divs to the scrollable container div
This is a screenshot of the div after adding boxes to it:
the box is created outside of viewport and is created at the top of the scrollable area
So to be able to maintain the viewport at the top of the scrollable area I am hoping to use refs to do ReactDOM.findDOMNode(this.songIdWrapper) and then manipulate the scrollTop or use scrollTo methods.
Please find the code snippet below:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
class AddPlaylist extends Component {
constructor(props){
super(props);
this.state = {
displaySearch: false,
id: '',
playlistName:'',
playlistTitle:'',
songs:[]
}
this.handleIdSubmit = this.handleIdSubmit.bind(this);
this.handleIdChange = this.handleIdChange.bind(this);
this.handleNamechange = this.handleNamechange.bind(this);
this.handleNameSubmit= this.handleNameSubmit.bind(this);
this.callback=this.callback.bind(this);
}
componentWillUpdate () {
console.log(ReactDOM.findDOMNode(this.songIdWrapper));
}
componentDidUpdate () {
}
callback (songId) {
this.songIdWrapper=songId;
}
render () {
return(
<div className='add-playlist-wrapper'>
<div className='form-wrapper container'>
<form onSubmit={this.handleNameSubmit} className='playlist-name-wrapper'>
<input className={this.state.submittedName ? 'hide-input' : ''} required onChange={this.handleNamechange} value={this.state.playlistName} placeholder='Playlist title'/>
{this.state.submittedName ? <p className='title'>{this.state.playlistTitle}</p> : null}
</form>
<form onSubmit={this.handleIdSubmit} className='add-id-wrapper'>
<div className='input-add-playlist'>
<input required onChange={this.handleIdChange} value={this.state.id} placeholder='Add song...'/>
<button type='submit' className='fabutton'>
<i className="add-button fa fa-plus-square-o fa-3x" aria-hidden="true"></i>
</button>
</div>
</form>
<div id='song-id-wrapper' ref={this.callback}>
{this.state.songs.map((song, i) => {
return (<div key={i} className='song'>
<p>{song}</p>
</div>
)
})}
</div>
</div>
</div>
)
}
handleIdSubmit (event) {
event.preventDefault();
const newState = this.state.songs.slice();
newState.push(this.state.id);
this.setState({
songs:newState
})
}
handleIdChange (event) {
this.setState({
id: event.target.value
})
}
handleNamechange (event) {
this.setState({
playlistName: event.target.value
})
}
handleNameSubmit (event) {
event.preventDefault();
this.setState({
playlistTitle: this.state.playlistName
})
}
}
export default AddPlaylist;
The error message I get is:
this is the error message stating that ref is not a prop
So I am quite new to react and as far as I'm aware this is an attribute on a div element not passed as a prop to a component. So I hope you can see my confusion as when I search google/stack-overflow I see a lot of comments relating to child components. I am fully aware string refs have been depreciated and that callbacks should be used but no matter what I try I cannot get rid of this error message.
Any help would be greatly appreciated.
I guess the issue is that you try to access the ref in the componentWillUpdate Hook. Because from the pure setup there is nothing wrong.
The componentWillUpdate Hook gets actually called before the next render cycle, which means you access the ref BEFORE your component gets rendered, which means that you always access the ref from the render cycle before. The ref gets updated with the next render cycle.
https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/tapping_into_componentwillupdate.html
I think you should do the scroll position handling AFTER the component did update, not before it will update!