Mock function is not called with on click method with enzyme - javascript

I'm trying to just test that a function is indeed invoked when a click action occurs on a link in my component. I keep receiving the error
Expected mock function to have been called, but it was not called.
But it works correctly in the browser.
I thought maybe there was an issue with the test finding the id I was asking it to look for, but using other methods I see it was able to access the element just fine.
The component
import { toggleEditMode } from './otherFile.js'
class PersonalInformation extends Component {
constructor(props) {
super(props);
this.state = {editMode: false}
this.toggleEditMode = toggleEditMode.bind(this);
}
render(){
const { editMode } = this.state;
return(
<div>
{!editMode &&
<div className="col-md-4 hidden-sm-down">
<a
id="editingToggleButton"
className="display-block"
role="button"
href="javascript:void(0);"
onClick={() => this.toggleEditMode()}
>
<span className="icon icon-sm dls-icon-edit" />
<span className="pad-1-l">Edit</span>
</a>
</div>
}
</div>
)
}
}
The toggleEdit method
export function toggleEditMode() {
this.setState({ editMode: !this.state.editMode })
}
The test
describe('edit', () => {
it('should switch to editMode with click', () => {
const toggleEditMode = jest.fn();
const wrapper = mount(
<PersonalInformation
toggleEditMode={toggleEditMode}
/>
);
wrapper.find('#editingToggleButton').simulate('click');
expect(toggleEditMode).toHaveBeenCalled();
});
}
I was able to log what it finds when using the find method and it returns the right element. But I can't seem to figure out how "it was not called".

In the test file you have assigned the mock function to the props but in the component you are using it as a class function.
So, in the test file, when the user clicks the HTML element it fires the class function and not the mocked one.

Related

Display data from an API when clicking a button. Using ReactJS

I am trying to display the data of each character when I click the Info Button.
I know it is because in the onButtonClickHandler function it can not see the state. I have also tried this.state.person but it gives me an error saying "can not read state". And if I try just state.person it will give me "undefined".
What is the best way to do that? Thank you
API Link: https://swapi.dev/people/
import React from "react";
export default class FetchActors extends React.Component {
state = {
loading: true,
person: null
};
async componentDidMount() {
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
this.setState({ person: data.results, loading: false });
}
render() {
if (this.state.loading) {
return <div>loading...</div>;
}
if (!this.state.person.length) {
return <div>didn't get a person</div>;
}
function onButtonClickHandler(state) {
console.log(state.person);
};
return (
<div>
<h1>Actors</h1>
{this.state.person.map(person =>(
<div>
<div>
{person.name}
<button onClick={onButtonClickHandler}>Info</button>
</div>
</div>
))}
<button onClick={onButtonClickHandler}>Enter</button>
</div>
);
}
}
Please correct me if I'm wrong
The most likely reason why you are seeing this is because of the way javascript internally works. The syntax:
function xyz() {
}
has an implicit this
Maybe try changing your code from:
function onButtonClickHandler(state) {
console.log(state.person);
};
to:
const onButtonClickHandler = () => {
console.log(this.state.person);
};
Further Reading: Here
You have defined your function onButtonClickHandler as a function that takes one argument, and logs the person property of that argument. The argument state in your function has nothing to do with the state of your component. As javascript sees it, they are two totally unrelated variables which just happen to have the same name.
function onButtonClickHandler(state) {
console.log(state.person);
};
When button calls onClick, it passes the event as the argument. So your onButtonClickHandler is logging the person property of the event, which obviously doesn't exist.
Since you are not using any information from the event, your function should take no arguments. As others have said, you should also move this function outside of the render() method so that it is not recreated on each render. The suggestion to use bind is not necessary if you use an arrow function, since these bind automatically.
export default class FetchActors extends React.Component {
/*...*/
onButtonClickHandler = () => {
console.log(this.state.person);
};
}
Inside render()
<button onClick={this.onButtonClickHandler}>Enter</button>
You could also define the function inline, as an arrow function which takes no arguments:
<button onClick={() => console.log(this.state.person)}>Enter</button>
If you are new to react, I recommend learning with function components rather than class components.
Edit:
Updating this answer regarding our comments. I was so caught up in explaining the errors from doing the wrong thing that I neglected to explain how to do the right thing!
I am trying to display the data of each character when I click the Info Button.
Once we call the API, we already have the info loaded for each character. We just need to know which one we want to display. You can add a property expanded to your state and use it to store the index (or id or name, whatever you want really) of the currently expanded item.
When we loop through to show the name and info button, we check if that character is the expanded one. If so, we show the character info.
Now the onClick handler of our button is responsible for setting state.expanded to the character that we clicked it from.
{this.state.person.map((person, i) =>(
<div>
<div>
{person.name}
<button onClick={() => this.setState({expanded: i})}>Info</button>
{this.state.expanded === i && (
<CharacterInfo
key={person.name}
person={person}
/>
)}
</div>
CodeSandbox Link
there are a few ways you can resolve your issue; I'll give you the more common approach.
You want to define your click handler as a class (instance) method, rather than declare it as a function inside the render method (you can define it as a function inside the render method, but that's probably not the best way to do it for a variety of reasons that are out of scope).
You will also have to bind it's 'this' value to the class (instance) because click handlers are triggered asynchronously.
Finally, add a button and trigger the fetch on click:
class Actors extends React.Component {
state = {
loading: false,
actors: undefined,
};
constructor(props) {
super(props);
this.fetchActors = this.fetchActors.bind(this);
}
async fetchActors() {
this.setState({ loading: true });
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
this.setState({ actors: data.results, loading: false });
}
render() {
console.log('Actors: ', this.state.actors);
return <button onClick={this.fetchActors}>fetch actors</button>;
}
}
Sometimes i takes react a min to load the updated state.
import React from "react";
export default class FetchActors extends React.Component {
state = {
loading: true,
person: null
};
async componentDidMount() {
const url = "https://swapi.dev/api/people/";
const response = await fetch(url);
const data = await response.json();
if(!data.results) { // throw error }
this.setState({ person: data.results, loading: false }, () => {
console.log(this.state.person) // log out your data to verify
});
}
render() {
if (this.state.loading || !this.state.person) { // wait for person data
return <div>loading...</div>;
}else{
function onButtonClickHandler(state) { // just make a componentDidUpdate function
console.log(state.person);
};
return (
<div>
<h1>Actors</h1>
{this.state.person.map(person =>(
<div>
<div>
{person.name}
<button onClick={onButtonClickHandler}>Info</button>
</div>
</div>
))}
<button onClick={onButtonClickHandler}>Enter</button>
</div>
);
}
}}

Enzyme - Testing onClick of child component

I very new to testing and I am currently testing React components with jest/enzyme.
I have a parent component
ParentComp.jsx
export class ParentComp extends React.Component {
super(props);
this.state = {
selectedTemplate: "",
disabled: true
};
return <Modal
header={<h2>Header</h2>}
visible={true}
footer={<span>
<Button
disabled={false}
onClick={ () => {
sessionStorage.setItem('id', this.state.id);
}}
>
Continue
</Button>
}>
<h1> My modal </h1>
</Modal>
}
How would I go about testing the onClick and making sure the sessionStorage is tested?
I've already tried:
ParentComp.spec.jsx
const wrapper = shallow(<ParentComp/>);
wrapper.find(Modal).first().props().footer.find(Button).simulate('click')
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();
// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
expect(global.sessionStorage.getItem).toBecalledWith('id',1)
}
I thankfully don't get any actual errors, however, my sessionStorage line in ParentComp is apparently not being covered. How would I go about covering this line?
I have an idea to call directly prop onClick directly in this case instead of simulating of click. As long as you can find your <Button /> in your footer then call its prop of onClick. Let's try:
// You can console.log to see what is the correct to select the right one
// I'm not sure below route is correct :)
const yourButton = wrapper.find(Modal).first().props().footer.props.children;
yourButton.props.onClick();

React Button onClick pass arguments

I am trying to call a function after the button has been clicked, which is possible so far but I have problems in passing the argument.
This is my first React App so bear with me.
In the this part the onClick event calling the "clickedQuickreply()" wont work
It fires a "TypeError: Cannot read property 'value' of undefined"
export function showMessage() {
console.log("Show Message");
let timeStamp = messages.giveTimestamp("not set");
let listItems = messageList.map(d => (
<p className={d.senderId} key={d.senderId}>
{" "}
{d.text}{" "}
</p>
));
let listreply = quickReplyList.map(d => (
<button
className="quickReplyButton"
key={d.id}
value={d.qrText}
**onClick={clickedQuickreply(this.value)}**
>
<span> {d.qrText} </span>
</button>
));
return (
<div>
<div className="timestamp">{timeStamp}</div>
<ul>{listItems}</ul>
<div className="quickreply">{listreply}</div>
</div>
);
}
export function clickedQuickreply(e) {
console.log("Clicked", e);
quickReplyList.length = 0;
//send.sendMessageToServer(query);
}
This is the code where it renders. Named App.js "main"
Normally I wanted to do the re-rendering everytime a fetch Request has completed, but my React understanding is not that far I guess.
class MessageDisplay extends React.Component {
constructor(props) {
super(props);
this.state = null;
}
componentDidMount() {
window.addEventListener("click", this.tick.bind(this));
window.addEventListener("keypress", this.tick.bind(this));
}
componentWillUnmount() {
window.removeEventListener("click", this.tick.bind(this));
window.removeEventListener("keypress", this.tick.bind(this));
}
componentDidUpdate() {}
tick() {
this.forceUpdate();
}
render() {
setTimeout(() => {
this.forceUpdate();
}, 2000);
return (
<div className="chatBox">
<ui.showMessage />
</div>
);
}
}
So how do you pass an argument in that situation for example?
Thanks for your time and patience.
You should write onClick={clickedQuickreply(this.value)} as onClick={clickedQuickreply}. React will execute the function by passing it the event object as argument internally.
One more thing I notcied here is that, you no need to export the function clickedQuickreply, as it can be private used as callback function as you did now by attaching it with the onClick props. So write it without export and define it inside the showMessage function.

React - set state using button and pass it as props

I want to use the 'compare' button to toggle the compare state to true or false.
Next I want to pass this compare state to pivot as props.
I am literally using the same code as in the react documentation when looking at the Toggle class. https://facebook.github.io/react/docs/handling-events.html
The only thing I changed is the name isToggleOn to compare.
When looking at the console client side I get following error every time the component renders:
modules.js?hash=5bd264489058b9a37cb27e36f529f99e13f95b78:3941 Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.`
My code is following:
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = { compare: true };
this.handleClick = this.handleClick.bind(this);
}
handleClick(button) {
if (button === 'compare') {
this.setState(prevState => ({
compare: !prevState.compare,
}));
}
}
render() {
return (
<Grid>
<div className="starter-template">
<h1>This is the dashboard page.</h1>
<p className="lead">
Use this document as a way to quickly start any new project.<br />{' '}
All you get is this text and a mostly barebones HTML document.
</p>
</div>
<ButtonToolbar>
<button onClick={this.handleClick('compare')}>
{this.state.compare ? 'AGGREGATE' : 'COMPARE'}
</button>
</ButtonToolbar>
<PivotTable
ready={this.props.isReady}
data={this.props.gapData}
compare={this.state.compare}
/>
</Grid>
);
}
}
export default (DashboardContainer = createContainer(() => {
// Do all your reactive data access in this method.
// Note that this subscription will get cleaned up when your component is unmounted
const handle = Meteor.subscribe('weekly-dashboard');
return {
isReady: handle.ready(),
gapData: WeeklyDashboard.find({}).fetch(),
};
}, Dashboard));
Any advice on how to fix this?
The reason is this line
<button onClick={this.handleClick('compare')}>
This will call the handleClick function while executing render function. You can fix by:
<button onClick={() => this.handleClick('compare')}>
Or
const handleBtnClick = () => this.handleClick('compare');
...
<button onClick={this.handleBtnClick}>
...
I prefer the latter

Call react component from outside

Is that possible to call react component from outside?
For example
HTML
<div id='react-app'></div>
<button onClick=callReactModal()>PressME</button>
My component where i want call method
let callReactModal = function () {
console.log('clicked');
//Navigation.sayHello();
}
class Navigation extends React.Component<any, any> {
constructor(props:any){
super(props);
this.state = {
language: {
lang: cookie.load('lang') || ''
}
};
this.click = this.click.bind(this);
}
sayHello = () => {
alert("Hello");
}
}
I have to call Modal from another component but i don't know how to achieve that.
Trying to call method which update state in class and getting Warning: setState(...): Can only update a mounted or mounting component. It needs to open modal ( Using semantic-ui )
method which uses state
handleOpen = (e) => this.setState({
modalOpen: true,
})
modalPart
<Modal size='small'
open={this.state.modalOpen}
onClose={this.handleClose}
trigger={<a className="btn btn-base" onClick={this.handleOpen}>Login</a>}
closeIcon='close'>
Thanks for help!
Although not the best practice. You have to set callReactModal function in the window
window.callReactModal = function () {
console.log('clicked');
//Navigation.sayHello();
}
A better way to implement it, is to create an event listener that opens the modal when triggered.
No, it's not possible. All your "custom tags"(React Components) should be inside your JSXs. But you can render multiple React apps per page if you want:
<div id='react-app'></div>
<div id='react-button'></div>

Categories