Firing a click event on a child component - reactjs

I have been trying to write a test that will ensure that when a marker is clicked (from leaflet) further details will be displayed to the user. The Marker component is a child of the Map component. To start I am just wanting to see if the onClick function is called once when the marker is clicked.
The Map component returns the following structure
return(
<LeafletMap>
<Marker data-testid='marker' onClick={someFunc}/>
<TileLayer/>
<Popup/>
</LeafletMap>
)
In my test I attempt to render the Map component and find the marker via a data-testid:
const handleParcelClick = jest.fn()
it('get parcel details upon clicking the marker', () => {
const {getByTestId}= render(<Map lat={someNumber} lng={someNumber} zoom={14} parcels={fakeParcels} activeParcel={fakeDetails} onParcelClick={handleParcelClick} />)
const marker = getByTestId('marker')
fireEvent.click(marker)
expect(handleParcelClick).toBeCalledTimes(1)
});
When attempting to run I get the following error:
at getElementError (node_modules/#testing-library/dom/dist/query-helpers.js:22:10)
at args (node_modules/#testing-library/dom/dist/query-helpers.js:76:13)
at getByTestId (node_modules/#testing-library/dom/dist/query-helpers.js:59:17)
at Object.<anonymous>.it (src/ParcelDetails.test.tsx:58:20)
I have attempted using enzyme as well with no success. The data-testid in the actual code is unique for each marker, called marker above for simplicity. Am I going about this wrong? Should I be testing the Marker separately from the Map component?
Update: I have attempted to use enzyme as a solution; however, I receive the following error when trying to simulate a click
TypeError: Cannot read property '__reactInternalInstance$k2volvgmsgj' of null
There does not seem to be a consistent solution for this error and I am confused as to why I am getting it. I have ensured that marker is the component I am wanting to click and that it is not null.
Here is my updated code:
it('Loads parcel details on click', ()=> {
const mockClick = jest.fn();
const component = mount(<Map lat={n1} lng={n2} zoom={14} parcels={fakeParcels} activeParcel={fakeDetails} onParcelClick={mockClick} />);
const marker = component.find(Marker).first();
marker.simulate('click');
expect(mockClick).toBeCalledTimes(1);
});

The easiest way to fire a click event on an element in jest is to first find the element using dom selector and then simulate click on it, like this:
let element = document.getElementById('your-element-id');
element.simulate('click');
Hope this helps!!

I was able to get the desired behaviour via enzyme. Although it is not the best solution - it will do for now. I know shallow rendering is not the best practice.
Here is a snippet of my solution using shallow from enzyme:
it('Loads parcel details on click', ()=> {
const onParcelClick = jest.fn();
const component = shallow(<Map lat={n1} lng={n2} zoom={14} parcels={mockParcels} activeParcel={mockDetails} onParcelClick={onParcelClick} />);
const marker = component.find(Marker).first();
marker.simulate('click');
expect(onParcelClick).toBeCalledTimes(1);
});

Related

React jest snapshot only returning empty DocumentFragment

I am using React and writing a Modal component like so:
const MyModal: FC<MyModalProps> = memo((props) => {
return (
<Modal isOpen={true} data-client-id={modal-client-id}>
...
</Modal>
);
});
I am trying to test this using testing-library and jest like so:
const { asFragment } = render(<MyModal {...myTestProps} />);
const renderFragment = asFragment();
expect(renderFragment).toMatchSnapshot();
However, when I check the snapshot I only see <DocumentFragment />. I can test whether the modal is there by a getByTestId(modal-client-id) and I can see that the modal is rendering and appearing when I run in Storybook with the exact same props. The snapshot also works and returns the inner components when I remove the surrounding Modal component. Is there a reason why Snapshot would only return the DocumentFragment and not the full snapshot? Would it just imply that within the unit test the component is not rendering?
Testing-Library render appends the passed in component to a parent div. This corresponds with the Container option of the render function. By default Testing-Library creates a div and appends to it. In the case of the modal, the modal is a separate pop up that was not being rendered as a child of the div, which explains why the fragment only rendered as:
<DocumentFragment>
<div />
</DocumentFragment>
To debug this, printing out the rendered screen gave the clue with: screen.debug(). This showed that the modal was being rendered outside of the container div, which is why other queries/gets were able to find components.
Alternatively, we can also override the baseElement option, which defaults to document.body if not specified. In my case because any modal rendered would correctly be rendered on top of a component, I instead did:
const result = render(<MyModal {...myTestProps} />);
const modalComponent = screen.getByTestId('modal-client-id');
expect(modalComponent).toMatchSnapshot();
This avoids messing around with attempting to specify the container or baseElement options since anything rendered will be rendered on top.

Not simulating 'change' using enzyme

I have a Reactjs component and it has one button and a date-range picker.
I want to simulate onclick and onchange events of button and picker respectively.
I'm able to simulate onclick of the button. but on change of datepicker is not working
I have tried this
headerComponent.find(`#prev_button`).at(1).simulate("click");
headerComponent.find(`#dropdown`).at(1).simulate("change", { value: "t" });
please see this sandbox click here for full code and test file
Based on Enzyme documentation you make a mistake on your second argument on simulate function.
To simulate changes on the input, you should change it like this :
headerComponent.find(`#dropdown`).at(1).simulate("change", { target: { value: "t" } });
Testing with enzyme is tricky. You should try not to test dependencies because you trust those are already tested. Having said that, you could shallow render instead of mounting and look for the RangePicker component in the shallow tree, get the handler you are passing in the onChange prop and call it manually, then check the callback prop you pass to your component is called with the expected value.
describe.only("test", () => {
it("should render", () => {
const callBackToSetDates = jest.fn();
const callBackToSetFilter = jest.fn();
const wrapper = shallow(
<Header
{...headerProps1}
callBackToSetDates={callBackToSetDates}
callBackToSetFilter={callBackToSetFilter}
/>
);
const rangePickerOnChange = wrapper.find("RangePicker").prop("onChange");
rangePickerOnChange("someValue");
expect(callBackToSetDates).toHaveBeenCalledWith("someValue");
});
});
the purpose is to test only the logic you add inside your component, i.e., you transform the value you get from the RangePicker to something else
<RangePicker
...
onChange={(value) => {
callBackToSetDates(`I'm transforming ${value}`);
}}
/>
and in your test
rangePickerOnChange("someValue");
expect(callBackToSetDates).toHaveBeenCalledWith("I'm transforming someValue");
you can see it working here https://codesandbox.io/s/cool-rosalind-uec6t?file=/src/tests/index.test.js
If you really want to keep testing what the actual user sees, you'll need to fire the events that the user does when using the component. In this case: you need to click the input, look for a date, click it, then click another date to completely fire the onChange event of the RangePicker component. You might look at how antd test it and copy the necessary jest configuration they have to mock some DOM APIs

How do I simulate a value change on a Material UI Slider?

I'm trying to test a state change via React Hooks (useState) by simulating a changed value on a Material UI slider, since that calls my hooks state update function. I'm then trying to verify the change by checking text that's displayed on the page. I feel like I've gotten close, no syntax errors yelling at me anymore, but it seems like the change isn't happening.
I've tried multiple different selectors and found that most people online are importing their Material UI element and using the imported item as the selector. I've tried this with and without .as(0), with a value of 500 as both a string and a number, and other variations.
My slider's onChange method directly calls my hooks state update method with the e.target.value to set as the new state value (and that all works fine in the actual app).
Any ideas of other things I can try? Or maybe there's another better way to test state changing with React hooks?
import ReactDOM from 'react-dom';
import App from './App';
import { createMount } from '#material-ui/core/test-utils';
import Slider from "#material-ui/core/Slider";
describe('App', () => {
let mount;
beforeEach(() => {
mount = createMount();
});
afterEach(() => {
mount.cleanUp();
});
it('updates the volts displayed when the slider value changes', () => {
const wrapper = mount(<App />);
wrapper.find(Slider).at(0).simulate('change', { target: { value: 500 } });
expect(wrapper.find('.volts-screen').text()).toEqual('0.500');
})
}```
ERROR
expect(received).toEqual(expected) // deep equality
Expected: "0.500"
Received: "0.000"
Ended up figuring it out. I imported the slider from material core (in the same way as in my component), and then I used the imported element in my wrapper.find and simulated a change with a second argument of { target: { value: 500 }}.
I thought this wasn't working at first, but realized that because in my case the effect was updating my component state which is async, I needed to add a setTimeout to make sure the update was captured before my check ran.
Saw some similar guidance on other Material UI elements, but didn't realize the slider would be so similar. Will leave this here just in case some other poor soul is scouring the internet for "Slider"-related info in particular ;)
//imported the element as below
import Slider from "#material-ui/core/Slider";
// found in wrapper using that element and simulated change with target value on timeout
it('updates the volts displayed when the slider value changes', () => {
const wrapper = mount(<App />);
wrapper.find(Slider).at(0).simulate('change', { target: { value: 500 } });
setTimeout(() => expect(wrapper.find('.volts-screen').text()).toEqual('0.500'), 0);
})
Just for general knowledge:
To set the value and interact not just with the component (i.e. the Context):
sliderToFind
.prop("children")[1]
.props.ownerState.onChangeCommitted(null, YOUR_VALUE);
sliderToFind.prop("children")[1].props.ownerState.value = YOUR_VALUE;
This is changing the value and firing the prop onChangeCommitted (or others i.e. onChange)

How to create an unit test for UncontrolledTooltip from reactstrap that does not handle state management directly?

I implemented simple UncontrolledTooltip from reactstrap. The doc (https://reactstrap.github.io/components/tooltips/) says
uncontrolled component can provide the functionality wanted without the need to manage/control the state of the component
If I want to implement an unit test (e.g. jest + enzyme) for testing its state as either open or close, how can I create a unit test without manually tinkering with state value? Is this possible to achieve it? It seems only possible with regular Tooltip component but I like to hear advice from seasoned engineers.
[Update]:
Upon request I include here tooltip and unit test I am trying to execute. At the moment, I want to simulate hover on the tooltip however mockHover.mock.calls.length returns as 0 which I interpret as mock function was not triggered.
Here is my Tooltip.
import React from 'react';
import { UncontrolledTooltip } from 'reactstrap';
export default class MyTooltip extends React.Component {
render() {
const { metaData, wg } = this.props;
return (
<div>
<UncontrolledTooltip placement="bottom" trigger={'hover'} target={wg}>
{metaData}
</UncontrolledTooltip>
</div>
);
}
}
Here is my unit test that use jest and enzyme:
describe('<MyTooltip />', () => {
it('Tooltip unit test', () => {
const mockHover = jest.fn();
const wrapper = shallow(<MyTooltip trigger={mockHover} />);
expect(wrapper.find(UncontrolledTooltip));
wrapper.find(UncontrolledTooltip).simulate('hover');
expect(mockHover.mock.calls.length).toEqual(1);
});
});
There are few important things to start from:
UncontrolledTooltip is part of 3rd party package so you won't test it explicitly.
Instead you better focus on testing your wrapper around UncontrolledTooltip.
simulate is nothing related to events browser's system. It's just a syntax sugar to do props().onHover(...). So if target component has such a prop - and it's a callback-function - it will be called. If there is no such a prop - it would be up to defaultProps what's going on. Anyway nothing like 'emulating mouse cursor over the element'.
shallow() will stop rendering at level of UncontrolledTooltip(its internals will not be rendered)
Keeping that in mind I see you able only:
your component finally renders UncontrolledTooltip with expected constant prop values
both metaData and wg props are passed down to UncontrolledTooltip
it('renders UncontrolledTooltips under the hood', () => {
const wg = '1';
const metaData = (<span>2</span>);
const wrapper = shallow(<MyTooltip wg={wg} metaData={metaData} />);
const innerTooltip = wrapper.find(UncontrolledTooltip);
/*
I don't validate `find(UncontrolledTooltip).toHaveLength(1)`
since assertion on `.find(..).props()` would throw exception otherwise
*/
expect(innerTooltip.props().placement).toEqual('bottom');
expect(innerTooltip.props().trigger).toEqual('hover');
expect(innerTooltip.props().wg).toEqual(wg);
expect(innerTooltip.props().metaData).toEqual(metaData);
});

How to use enzyme ShallowWrapper to find a React Component stored as a prop in another React Component?

I have a jest/enzyme test which creates a ShallowWrapper around a component, finds a specified semantic-ui-react Button (by id), simulates a click on the button, and then looks to see if the click toggled certain content.
Sample JSX:
<Popup
trigger={<Button onClick={this.toggleShowThing} id="special-btn">a button</Button>}
content="Popup Words"
/>
{this.state.showThing &&
<div className="special-thing">The Thing's Words</div>
}
Sample Test:
it('shows the thing when the button is clicked', () => {
const wrapper = shallow(<MyComponent />);
wrapper.find('#special-btn').simulate('click', { preventDefault() {} });
expect(wrapper.find('.special-thing').exists()).toBe(true);
});
This test worked when I just had the Button. When I added the Popup and the Button was placed into the trigger prop then I received an error because #special-btn could not be found.
Error: Method “props” is only meant to be run on a single node. 0 found instead.
An enzyme snapshot of the component shows that the Popup looks like this:
<Popup
content="Popup Words"
on="hover"
position="top left"
trigger={
<Button
id="special-btn"
onClick={[Function]}
>
a button
</Button>
}
/>
I need my test to work again. How do I gain access to the #special-btn again in the test so that I can call .simulate('click') on it?
This is what worked for me, although there is no documentation for it:
import {shallow, ShallowWrapper} from "enzyme";
it('shows the thing when the button is clicked', () => {
const wrapper = shallow(<MyComponent />);
const button = new ShallowWrapper(
wrapper.find('Popup').prop('trigger'), wrapper
);
button.simulate('click', { preventDefault() {} });
expect(wrapper.find('.special-thing').exists()).toBe(true);
});
In other words:
Find the Popup component.
Get the component rendered in its trigger prop. Note that this is not yet a shallow wrapper, so no fancy APIs yet.
Manually create the wrapper using ShallowWrapper (it's important to pass the second argument).
Now you can access all the enzyme APIs to interact with the button.
Note, it seems that you can avoid using the constructor and use wrap() utility method instead (also not documented):
const button = wrapper.wrap(wrapper.find('Popup').prop('trigger'));
Assuming Popup is some third-party component that has already been tested, I would approach testing the following way:
(1) Find the Popup and check if the trigger prop's Button's onClick prop is componentWrapper.instance().toggleShowThing
(2) As a separate thing, set this.state.showThing to false and verify no div with className special-thing is rendered; set this.state.showThing to true and verify it is rendered.
(*) this.toggleShowThing should also be tested on its own.
The problem is that you cannot do it. You need to rewrite your test. Your button is now wrapped by a Popup component thus you don't have access to it. But you can move your selector to the Popup and test if clicking the popup triggers required change. There's no other way around.
// JSX
<Popup
trigger={<Button onClick={this.toggleShowThing} id="special-btn">a button</Button>}
content="Popup Words"
id="popup"
/>
{this.state.showThing &&
<div className="special-thing">The Thing's Words</div>
}
// test
it('shows the thing when the button is clicked', () => {
const wrapper = shallow(<MyComponent />);
wrapper.find('#popup').simulate('click', { preventDefault() {} });
expect(wrapper.find('.special-thing').exists()).toBe(true);
});

Resources