Update inside a test was not wrapped in act - reactjs

I'm wrapping everything I can into act, but this error comes constantly until the test times out.
it('select a chain and type a name', (done): * => {
let result;
act(() => {
result = render(
<AddNewChainDialog
title="Add new chain dialog"
projectId="fjhkldgs"
closeCallback={ret => {
console.log(ret);
done();
}}
/>
);
});
jest.advanceTimersByTime(1000);
// expect(result == null).toBe(false);
act(() => {
fireEvent.change(
result.getByLabelText('chainNameInput', {
target: { value: 'test chain' }
})
);
});
act(() => {
fireEvent.change(
result.getByLabelText('masterTemplateChoice', {
target: { value: 'ID1' }
})
);
});
act(() => {
// fireEvent.click(result.getByText('Add Template'));
});
});
My component is doing an update when mounted, so I think it's coming from that but I have not idea how to manage act with that

Related

Reactjs passing data from child to parent

There is no problem in adding the because I successfully added data to my database but the problem is need to refresh the page before getting the updated data. this is the code ive done so far, this is the current error, did i miss something in my code? please help
note:
The child component will add data and the parent component will
display the latest data that the child component inserted
parent
const OvertimeType = () => {
const [reRender, setRerender] = useState(false);
....
const fetchData = async () => {
const response = await getDepartmentData('All', 100, 0);
response.data.map(function(u){
.....
})
}
useEffect(() => {
fetchData();
}, [reRender]);
const HandleAdd = (val) =>{
setRerender(val);
}
return (
<CustomToolbar sendToParent={HandleAdd()}/>
)
...
}
//
child
class CustomToolbar extends React.Component {
state = false;
HandleAdd = () => {
Swal.fire({
title: 'Add Over Time',
text: "Input overtime name below.",
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Save',
html: generateInputForms({
strname: '',
intsequence: ''
}),
preConfirm: () => {
let strname = document.getElementById('strname').value;
let intsequence = document.getElementById('intsequence').value;
if (!strname) {
Swal.showValidationMessage('This field is required.')
}
if (!intsequence) {
Swal.showValidationMessage('This field is required.')
}
return {
strname: document.getElementById('strname').value,
intsequence: document.getElementById('intsequence').value
}
}
}).then((result) => {
if (result.isConfirmed) {
let request = {
strname:document.getElementById('strname').value,
intsequence:document.getElementById('intsequence').value
}
addDepartment(request).then(res =>{
if (res.status == 200){
Swal.fire({
icon: 'success',
title: 'Over Time',
text: 'New Data has been added successfully.',
}).then(res => {
this.sendToParent(res);
})
}else{
Swal.fire({
icon: 'error',
title: 'Oops',
text: 'Something went wrong.',
})
}
})
}
})
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<Tooltip title={"Add"}>
<Button
variant="contained"
onClick={this.HandleAdd}
className={classes.button}
startIcon={<AddIcon className={classes.addIcon} style={{color: '#fff',}} />}
>
Add
</Button>
</Tooltip>
</React.Fragment>
);
}
}
The problem is probably the way you pass the function
<CustomToolbar sendToParent={HandleAdd()}/>
A callback function should be sent like this without the parenthesis:
<CustomToolbar sendToParent={HandleAdd}/>
I see two issues here. First, as a few others have noted you'll want to pass the function, not invoke it.
<CustomToolbar sendToParent={HandleAdd}/>
Second, in this implementation sendToParent is defined on props, rather than a function in the class. To access it, you'll need to invoke it on this.props.
this.props.sendToParent(res);

Jest React - else path not taken

I'm trying to add test coverage for this React component and I'm getting this message on the component "else path is not taken" when there is no else after if statement.
Below is my component with jest warning. Can someone help to cover this part?
function CustomerSatisfaction(props: FeedbackProps) {
const [status, setStatus] = useState<
'idle' | 'active' | 'pending' | 'success' | 'error'
>('idle');
const handleSubmit = useCallback(
async (smileyType: string, options: Record<string, string>) => {
setStatus('pending');
try {
const result = await fetchWithError(() => {
setStatus('error');
})('/pub/feedback/feedbacks', 'POST', {
surveyType: 'service',
smileyType,
comments: options.comment,
ratings: {
clearness: options['customer_satisfaction.clear'],
ease: options['customer_satisfaction.easy'],
speed: options['customer_satisfaction.fast'],
performance: options['customer_satisfaction.perf'],
},
pageUrl: window.location.href,
serviceId: props.serviceId,
productId: props.productId,
});
**(else path not taken)** if (result.success) {
setStatus('success');
}
} catch (e) {
setStatus('error');
}
},
[],
);
return (
<CustomerSatisfactionComponent
i18n={props.i18n}
status={status}
onSubmit={handleSubmit}
/>
);
}
Guys if someone faces this issue, here is the solution for my case
it('should render failure state', async () => {
const component = shallow(
<CustomerSatisfaction
i18n={() => ({})}
serviceId="123"
productId="123"
/>,
);
(fetchWithError as jest.Mock).mockReturnValue(
jest.fn().mockResolvedValue({
success: false,
}),
);
const onSubmit: any =
component.find(CustomerSatisfactionComponent).prop('onSubmit') ||
(() => {});
await onSubmit('test', {});
expect(component).toMatchSnapshot();
});

fireEvent is calling Found multiple elements by: data-testid error in react-testing-library

I'm calling a function by finding the button with the data-testid with "show_more_button"
<OurSecondaryButton test={"show_more_button"} onClick={(e) => showComments(e)} component="span" color="secondary">
View {min !== -1 && min !== -2 ? min : 0} More Comments
</OurSecondaryButton>
showComments
const showComments = (e) => {
e.preventDefault();
if (inc + 2 && inc <= the_comments) {
setShowMore(inc + 2);
setShowLessFlag(true);
} else {
setShowMore(the_comments);
}
};
which renders this
const showMoreComments = () => {
return filterComments.map((comment, i) => (
<div data-testid="comment-show-more" key={i}>
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
));
};
and upon executing fireEvent the function gets called which is good but, im getting the error:
TestingLibraryElementError: Found multiple elements by:
[data-testid="comment-show-more"]
(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).
There is only one data-testid with "comment-show-more", i doubled checked, this function must be getting triggered multiple times in the same test i guess, Im not sure..
CommentList.test.tsx
it("should fire show more action", () => {
const { getByTestId } = render(<CommentList {...props} />);
const showMore = getByTestId("show_more_button");
fireEvent.click(showMore);
expect(getByTestId("comment-show-more").firstChild).toBeTruthy();
});
CommentList.test.tsx (full code)
import "#testing-library/jest-dom";
import React, { Ref } from "react";
import CommentList from "./CommentList";
import { render, getByText, queryByText, getAllByTestId, fireEvent } from "#testing-library/react";
import { Provider } from "react-redux";
import { store } from "../../../store";
const props = {
user: {},
postId: null,
userId: null,
currentUser: {},
ref: {
current: undefined,
},
comments: [
{
author: { username: "barnowl", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 520,
postId: 28,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 9,
},
{
author: { username: "barnowl", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 519,
postId: 27,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 10,
},
{
author: { username: "barnowl2", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 518,
postId: 28,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 11,
},
],
deleteComment: jest.fn(),
};
describe("Should render <CommentList/>", () => {
it("should render <CommentList/>", () => {
const commentList = render(<CommentList {...props} />);
expect(commentList).toBeTruthy();
});
it("should render first comment", () => {
const { getByTestId } = render(<CommentList {...props} />);
const commentList = getByTestId("comment-list-div");
expect(commentList.firstChild).toBeTruthy();
});
it("should render second child", () => {
const { getByTestId } = render(<CommentList {...props} />);
const commentList = getByTestId("comment-list-div");
expect(commentList.lastChild).toBeTruthy();
});
it("should check comments", () => {
const rtl = render(<CommentList {...props} />);
expect(rtl.getByTestId("comment-list-div")).toBeTruthy();
expect(rtl.getByTestId("comment-list-div")).toBeTruthy();
expect(rtl.getByTestId("comment-list-div").querySelectorAll(".comment").length).toEqual(2);
});
// it("should match snapshot", () => {
// const rtl = render(<CommentList {...props} />);
// expect(rtl).toMatchSnapshot();
// });
it("should check more comments", () => {
const { queryByTestId } = render(<CommentList {...props} />);
const commentList = queryByTestId("comment-show-more");
expect(commentList).toBeNull();
});
it("should fire show more action", () => {
const { getByTestId } = render(<CommentList {...props} />);
const showMore = getByTestId("show_more_button");
fireEvent.click(showMore);
expect(getByTestId("comment-show-more").firstChild).toBeTruthy();
});
});
Try to clean up the DOM after each test
import { cleanup } from '#testing-library/react'
// Other import and mock props
describe("Should render <CommentList/>", () => {
afterEach(cleanup)
// your test
}
Note: You have filterComments.map so make sure filterComments have one item.
use
getAllByTestId
example:
await waitFor(() => userEvent.click(screen.getAllByTestId('serviceCard')[0]));
Kinda late but this may be helpful for somebody:
I can see that you are using a iterator that might return multiple children, if you want to solve differently, add a literals key for each child when defining your data-testid tag:
const showMoreComments = () => {
return filterComments.map((comment, i) => (
<div data-testid={`comment-show-more-test-key-${i}`} key={i}>
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
));
};
It can be solved by use getAllByTestId.
it("should fire show more action", () => {
const { getAllByTestId, getByTestId } = render(<CommentList {...props} />);
const showMore = getAllByTestId("show_more_button")[0];
fireEvent.click(showMore);
expect(getByTestId("comment-show-more").firstChild).toBeTruthy();
});

react native undefined is not an object, but I defined it

Hi so I want to pass function to a functional component to execute. But i Get error undefined is not an object (evaluating '_this2.deleteChat')
Where I made mistake?
I often get this problem when using class components.
CLASS COMPONENT:
constructor(props) {
super(props);
this.state = { chats: [], usersData: [], userIDs: [], deletedChat: false };
this.getChats = this.getChats.bind(this);
this.deleteChat = this.deleteChat.bind(this);//DEFINED
}
deleteChat = chatID => {
const myId = firebase.auth().currentUser.uid;
let deleteData = firebase
.database()
.ref('/chats/' + chatID + '/members/')
.remove(myId);
this.setState({ ...this.state, deleteChat: !this.state.deletedChat });
};
...
return(
<TouchableHighlight
style={{ width: 65, height: 50 }}
title={data}
onPress={() =>
TwoButtonAlert(
'Do you realy wish to leave conversation?',
this.deleteChat(id),
)
}
>
...
My functional component
import React, { useState } from 'react';
import { Alert } from 'react-native';
const createTwoButtonAlert = (title, deleteChat) =>
Alert.alert(
title,
'My Alert Msg',
[
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed'),
style: 'cancel',
},
{ text: 'OK', onPress: () => deleteChat },
],
{ cancelable: false },
);
export default createTwoButtonAlert;
I have similar method but it runs in componentDidMount() and it works.
EXAMPLE:
componentDidMount() {
var _userId = firebase.auth().currentUser.uid;
this.getChats(_userId);
}
getChats = _userId => {
let data;
let usersData = [];
var readedData = firebase
.database()
.ref('chats')
.orderByChild('members/' + _userId)
.equalTo(true);
readedData.once('value', snapshot => {
data = snapshot.val();
// const temp = { ...data };
const filtered = Object.entries(data).map(([key, value]) =>
Object.keys(value)
.filter(value => value !== _userId)
.filter(value => value !== 'members')
.filter(value => value !== 'messages'),
);
this.setState({ chats: data, usersData: usersData, userIDs: filtered });
return true;
});
};
Two issues
You invoke deleteChat immediately when you pass it as this.deleteChat(id), which returns undefined since it is a VOID return
Attach it incorrectly in createTwoButtonAlert, i.e. () => deleteChat is improper syntax
Solution, convert to curried function to enclose the id in callback scope
deleteChat = chatID => () => {...
This will invoke the function immediately still, but this returns a function to be used as the callback.
Use the correct syntax to assign callback
onPress: () => deleteChat() // proxied callback
or
onPress: deleteChat // directly set the callback

How to use prevState so I don't have to use a setTimeout

How to use prevState on 'this.state.lat' & 'this.state.lng' so that I can remove the setTimeout from this peice of code below:
getCords(authUser) {
setTimeout(() => {
this.props.firebase.cats().push({
text: this.state.text,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9',
userId: authUser.uid,
lat: this.state.lat,
lng: this.state.lng,
})
this.setState({ ...INITIAL_STATE });
}, 150)
}
Without the setTimeout I get the incorrect values, so I think using prevState in replace of setTimeout should fix this issue? Any pointers are much appreciated!
Full code:
import React from 'react';
import { AuthUserContext, withAuthorization } from '../Session';
import Geocode from "react-geocode";
const INITIAL_STATE = {
text: '',
lat: '',
long: '',
address: ''
}
class AddCat extends React.Component {
constructor(props) {
super(props);
this.state = {
...INITIAL_STATE
}
}
componentDidMount() {
// set Google Maps Geocoding API for purposes of quota management. Its optional but recommended.
Geocode.setApiKey(process.env.REACT_APP_GOOGLEGEOCODEKEY);
// set response language. Defaults to english.
Geocode.setLanguage("en");
// set response region. Its optional.
// A Geocoding request with region=es (Spain) will return the Spanish city.
Geocode.setRegion("es");
// Enable or disable logs. Its optional.
Geocode.enableDebug();
// Get latidude & longitude from address.
}
getCords(authUser) {
setTimeout(() => {
this.props.firebase.cats().push({
text: this.state.text,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9',
userId: authUser.uid,
lat: this.state.lat,
lng: this.state.lng,
})
this.setState({ ...INITIAL_STATE });
}, 150)
}
onCreateCat = (e, authUser) => {
Geocode.fromAddress(this.state.address).then(
response => {
const { lat, lng } = response.results[0].geometry.location;
this.setState({lat: lat, lng: lng},
this.getCords(authUser)
);
},
error => {
console.error(error);
}
)
e.preventDefault();
}
onChangeText = e => {
this.setState({ text: e.target.value });
};
onChangeAddress = e => {
this.setState({ address: e.target.value });
};
render() {
console.log(this.state);
return (
<div>
<h1>Add cat</h1>
<AuthUserContext.Consumer>
{authUser => (
<div>
<form onSubmit={e => this.onCreateCat(e, authUser)}>
<input
type="text"
value={this.state.text}
onChange={this.onChangeText}
placeholder="Cats Name"
/>
<input
name="address"
value={this.state.address}
onChange={this.onChangeAddress}
type="text"
placeholder="Cats Postcode">
</input>
<button type="submit">Send</button>
</form>
</div>
)}
</AuthUserContext.Consumer>
</div>
);
}
}
const condition = authUser => !!authUser;
export default withAuthorization(condition)(AddCat);
If you look at the signature of the setState method it takes an updater and a callback that will run after the state gets updated:
setState(updater[, callback])
So you could call your this.getCords function inside of a this.setState callback, like so:
onCreateCat = (e, authUser) => {
e.preventDefault()
Geocode.fromAddress(this.state.address).then(
response => {
// ...
this.setState({ lat, lng }, () => {
// call methods of your component
// that rely on latest state values
// for `lat` and `lng` here
this.getCords(authUser)
})
}
)
}
Then, inside of the getCords method, you'd just do whatever you need to do without introducing "hacks":
getCords = (authUser) => {
this.props.firebase.cats().push(...)
this.setState({ ...INITIAL_STATE })
}
Here's a quick demo:
CodeSandbox
Hope this helps.

Resources