Get child props in Jest? (NO ENZYME) - javascript

I'm trying to test whether a React button component's text changes on-click. This is roughly what gets returned inside the component I'm testing (let's call it RandComponent):
return (<>
...
<ButtonComponent
data-testid='testButton'
>
{getButtonText()}
</ButtonComponent>
</>)
I want to be able to access whatever getButtonText() returns in Jest. I've looked online and in enzyme you can do something like this in the test file:
it('test button text toggle', async ()=>{
let container;
await act(async ()=> container = render(<RandComponent/>));
const button = getByTestId('testButton');
expect(button.text()).toEqual(whatever getButtonText() is supposed to return);
}
My problem is that I can't use enzyme... is there a way to get the child prop text like in the last line (button.text()) in Jest?

This requires to stub ButtonComponent by means of Jest and assert props that were provided to it:
jest.mock('...ButtonComponent module...', () => jest.fn());
...
let buttonProps;
ButtonComponent.mockImplementation(props => {
buttonProps = props;
return <div>ButtonComponent</div>
});
container = render(<RandComponent/>));
...
expect(buttonProps.children).toBe(...);
This is boilerplate code that Enzyme is supposed to save from.

Related

Insufficient Jest coverage

I have the below code:
<ReturnToLastSearch
href={'/listings'}
onClick={(e): void => {
e.preventDefault();
router.back();
}}
/>
Inside ReturnToLastSearch({ href, onClick }: Props) has the following:
<a className="button" onClick={!onClick ? urlOnClick : onClick}>
Everything is working fine, except jest complains that my diff, which adds the onClick prop and the ternary has insufficient tests coverage!
I tried:
it('when onClick is defined uses onClick', () => {
const call = ()=> ({back: jest.fn()});
jest.mock('next/router', call)
const { getByText } = render(<ReturnToLastSearch href="foo" onClick={call}/>);
getByText(i18n.t('favorites.returnToLastSearch') as string).click();
expect(router.back).toHaveBeenCalledWith('/listings')
});
I tried the above, which is the same as the version without onClick except for the expect and the mock of router.back(). However, I got an error telling me that the second argument in the jest.mock() should be an inline function.
What sort of test would make sense and also convince jest to leave me alone?
Thank you
I ended-up with:
it('when onClick is defined uses onClick', () => {
const call = jest.fn();
const { getByText } = render(<ReturnToLastSearch href="foo" onClick={call} />);
getByText(i18n.t('favorites.returnToLastSearch') as string).click();
expect(call.mock.calls.length).toBe(1);
});
It is not the most useful test, in my opinion. Yet, it did get jest off my back.

Pass props to React component stored in a variable extracted out of props.children

I want to insert some props to a React component which I have extracted out of props.children, like so:
<PageContainer>
<PageOne />
<PageTwo />
<PageThree />
</PageContainer>
Inside <PageContainer> i am extracting the current page via props.children and current page index, something like this:
const { children, pageIndex } = props;
let activePage = React.Children.toArray(children)[pageIndex];
Inside this PageContainer I have the "oportunity" to send down a function that I need inside the <PageOne>, <PageTwo> and <PageThree>. I tried something like this, but then I got some compiling problems, I think. Since this worked locally and not in my test environment:
const newProps = React.cloneElement(activePage.props.children, { myWantedFunction: myWantedFunctionThatIsAvailableInsidePageContainer });
activePage = React.cloneElement(activePage, { children: newProps });
The problem here is that myWantedFunction is not working in the test environment, but it is working locally. It says that it is not a function in the test environment, but when I console.log it out locally, it prints out a function. I am guessing there is a compiling problem, but I am wondering if there is a better way to acheive this (send down props to a component stored in a variable that I got out of props.children)?
Thank you in advance!
You are almost correct, you need to use React.cloneElement() to send props down to the child component. First you need to get the active page which you're doing right:
let activePage = React.Children.toArray(children)[pageIndex];
Then you need to use React.cloneElement to pass props to this component like this. Let's the name of the prop is isAuthenticated which is a boolean:
let component = React.cloneElement(activePage, { isAuthenticated: true })
Now in your page you'll be able to access this prop: prop.isAuthenticated
I created a working demo for you, have a look:
https://codesandbox.io/s/determined-kowalevski-eh0ic?file=/src/App.js
Hope this helps :)
Alternatively you could also do something like this:
const PAGES = [PageOne, PageTwo, PageThree]
function PageContainer(props){
const {pageIndex} = props
const ActivePage = PAGES[pageIndex]
return <ActivePage someFunction={someFunction} />
function someFunction(){
// the function you want to pass to the active page
}
}
Node that ActivePage has a capital A, which allows it to be used as a JSX component.
A drawback of this solution is however that if you use typescript it wont type check the props correctly since you only know which component is to be rendered at runtime. You could replace the array lookup with a switch to avoid that issue.
Yet another variation would be to just let the Page components handle their own state and pass the function to all of them. Like this:
function PageContainer(props){
const {pageIndex} = props
const someFn = () => 0
return <React.Fragment>
<PageOne id={1} activePage={pageIndex} someFunction={someFn} />
<PageTwo id={2} activePage={pageIndex} someFunction={someFn} />
<PageThree id={3} activePage={pageIndex} someFunction={someFn} />
</React.Fragment>
}
then in the Page Components just check if the pageIndex corresponds to their id:
function PageOne(props){
const {id, activePage, someFunction} = props
if (id === activePage){
const result = someFunction()
return <div>Page One: {result}</div>
}
}

Rerendering with react-testing-library is not working

This is my test
it('should run', async () => {
const { container } = renderWithContainers(<Wrapper />)
window.innerHeight = 50
await wait(() => fireEvent(window, new Event('resize')))
expect(getByTestId(container, 'myComponent')).toHaveStyle(`position: relative`)
})
This fails because the original position is fixed and im clearly using the container defined before the resize event fires...so how can I get it to "rerender" to use the updated container?
in my code once it rerenders it then has a different position (just not in the test)
I've done this:
const { container, rerender } = renderWithContainers(<Wrapper />)
window.innerHeight = 50
await wait(() => fireEvent(window, new Event('resize')))
rerender(<Wrapper />
but this breaks with invariant Violation: Could not find "store" react testing library but I have wrapped up all that store/provider logic inside renderWithContainers so how do I reuse that?
I want to do something like this: rerender(renderWithContainers(<Wrapper />)) but clearly that wont work
just to be ultra clear, I want to rerender my component after event listener has fired so I can see the updated component

Can I call a method without using .simulate() - Jest Enzyme

I'm unit testing for a React flight seat selecting app using Jest/Enzyme. Is there a way I can test a method within my class based component which would run after a button is clicked, but without actually simulating the button click? This is because the button is within a child of a child component and the method is being passed down as a prop.
Albeit a very simple function, I'd still like to test it
inputSeats(chosenSeats) {
this.setState({
chosenSeats: chosenSeats
})
}
This is in a parent component called FlightSeats, with a child of SeatMaps, and SeatMaps has 2 children of SeatMap (inbound/outbound).
Within each of these SeatMap components there is the 'Reserve Seats' button which when clicked it performs some validation tests, calls another method in SeatMap and eventually calls inputSeats() from within SeatMaps component. I'm struggling to simulate the button click since it is deep within the app.
Ideally, in my unit test, I'd just to like to call it with something like
FlightSeats.inputSeats(chosenSeats)
and pass in my mock data as chosenSeats... Or would I have to import the child components and mount them and use .simulate('click') on the button?
My test so far:
let chosenSeats = {
outbound: [{
seatNo: "11A",
seatPrice: "11.11",
}, {
seatNo: "12A",
seatPrice: "12.12"
}],
inbound: [{
seatNo: "14A",
seatPrice: "14.14",
}, {
seatNo: "15A",
seatPrice: "15.15"
}]
};
let wrapper, buildEmptySeats, clearSeats, comp;
beforeEach(() => {
comp = ( < FlightSeats seats = {
seatsArr
}
party = {
partyArr
}
flights = {
flightArr
}
/>);
wrapper = shallow(comp); component = mount(comp);
});
test('should handle inputSeats correctly', () => {
// what to call here??
expect(component.state().chosenSeats.outbound[1].seatNo).toEqual("12A");
expect(component.state().chosenSeats.outbound[1].seatPrice).toEqual(12.12);
});
I assume you are just testing the functionality, then it should be fine to directly call the method in parent component but its usually good to test the button clicked simulation process as this what the user clicks.
Here is a simple example based on your code above
const chosenSeats = {...};
const component = mount(comp);
test('should handle inputSeats correctly', () =>{
//here get the instance of the component before you can call its method
component.instance().inputSeats(chosenSeats);
//here you call state like you've already done
expect(component.state().chosenSeats.outbound[1].seatNo).toEqual("12A");
expect(component.state().chosenSeats.outbound[1].seatPrice).toEqual(12.12);
};

React-jest-enzyme: testing callback of child component that calls another function first before calling the callback

I am testing wether a callback that is passed to a child component is called after my button in the child component is clicked. I simulate the react-bootstrap button, <Button></Button>, by using the .simulate('click') function.
The problem is that the onClick() function of my button calls another function called update() and that function calls the handleSave callback passed to my child component. The onKeyPress function of the <FormControl/> element also calls the update function of my component. Here is how I have my child component setup:
update(event) {
//Have to check to see if the key stroke is the enter key or if it comes from the button click.
if(event.charCode === 13 || event.type === 'react-click'){
// Have to use this get the ref value because this.refs.input.value doesn't work.
var input = ReactDOM.findDOMNode(this.refs.input);
input.value = '';
this.props.handleSave();
}
}
render(){
return(
<Form>
<FormControl type="text" ref="input" onKeyPress={this.update.bind(this)} placeholder="Enter Note" />
<Button onClick={this.update.bind(this)}>Submit </Button>
</Form>
)
}
That is why my update() function has a check to see if came from charCode==13, that is the charCode for the enter key, or the button click because both save the info that is in the <FormControl />
I have my test setup this way:
describe('Input', () => {
const mockHandleText = jest.fn();
const mockHandleSave = jest.fn();
const props = {handleSave: mockHandleSave}
let input = shallow(<Input {...props} />);
describe('when entering a note', () => {
beforeEach(() => {
input.find('Button').simulate('click', {
charCode: 13
});
});
it('adds the note to state', () => {
expect(props.handleSave).toHaveBeenCalled();
});
});
});
A weird thing is that I have to pass an object as a second parameter to the .simulate() function because if I don't it will give me an error saying cannot read charCode of undefined but when a pass an object, the object doesn't even have to have an event property, then it just says
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.
Also If I don't pass in the object with some property then it also breaks my other test that I have for a callback on the onChange function of my element. I left it out of the code sample for the sake of simplicity and just uploaded the code that is giving me problems. I am also using a bootstrap form with and . The full code is on my github at github.com/Alebron23.
Enzyme's shallow method doesn't render the whole DOM tree, just the most shallow level. You'll not be able to find nested children using it. In the docs for shallow (https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md), they discuss that if you need to assert any behavior on child components, you'll have to use something other than shallow().
Your other options are to either use render(), or more likely- since render() is static and you want to test side effects- to fully mount()
the component (https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md) instead.

Categories