React jest snapshot only returning empty DocumentFragment - reactjs

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.

Related

React Testing Library: how to use screen in this case instead of container

in react testing library, as per the conventions, it is better to use screen than to destructure the methods from render.
I have a component, where I need to test the text that is rendered. The top level container in this component is a div, so there is no accessible query to get the text match. How do I use screen to do a text match?
I have currently resorted to using container from the destructured props, and then doing a text match on container.
// MyComponent - simplified
const MyComponent = () => <div> some text </div>
// test
const { container } = render(<MyComponent />);
expect(container).toHaveTextContent(...)
Reference: https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen
In the blog post it is mentioned that destructuring is mostly supported for legacy purposes, but there are no use cases which can't be solved using screen. Is this use case solvable using screen?
You should wrap the item in its specified role (which can be found here).
div is not defined as a role so you can change it to
h1 which will make it semantic and will also be easier to test. Here is how that will look like
function App(){
return <h1>Some random text</h1>
}
// Test
describe("App component", () => {
it("renders heading", () => {
render(<App />);
expect(screen.getByRole('heading',{level:1})).toBeInTheDocument()
});
});
A similar answer can be found here

Firing a click event on a child component

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);
});

Jest test open react-select menu

I have a component that uses react-select. I want to test the correct props are being passed to it, and those are being displayed correctly. Is there anyway to force the menu to open in enzyme/jest?
You can force change the state of the internal StateManager component
const tree = mount(<MyComponent />);
tree.find('Select').find('StateManager').instance().setState({ menuIsOpen: true });
tree.update();
Alternatively, a better way is to check the props of the component without opening the Select menu. This let's you abstract out react-select better in your tests.
const tree = mount(<MyComponent />);
// Run tests against options prop of Select
// expect(tree.find('Select').props('options')).toHaveLength(10);

How to test forwarded ref in React using test renderer or enzyme?

I use some amount of animations in my application and all animations depend on the refs (I am using GSAP). Most of the tested elements are located in other React components; so, I have set up forwardRef in my components to to pass the ref to the needed elements.
Now, I want to test these refs using Jest and Enzyme or React Test Renderer but I have not been able to achieve it with both the libraries. Here is my current implementation:
it('passes refs to the container component', () => {
const ref = React.createRef();
const div = document.createElement('div');
ReactDOM.render(<Row ref={ref} />, div);
const element = div.querySelector('div');
expect(element).toBe(ref.current);
});
Using ReactTestRenderer.create or enzyme.render to do a full rendering does not work because the refs are empty. Then, I found enzyme.mount function, which does something similar to ReactDOM.render; so, decided to use that:
it('passes refs to the container component', () => {
const ref = React.createRef();
const wrapper = mount(<Row ref={ref} />);
const div = wrapper.find('div').first().getDOMNode();
expect(div).toBe(ref.current);
});
This test does not pass because for some reason, the div returns HTMLElement while ref.current return something called WrapperComponent instead of returning the forwarded element.
Creating element and using ReactDOM to render and test the ref works fine but I would want to know if there is a way to use enzyme or react test renderer for this purpose (I am not a fan of using multiple libraries for rendering to test different functionalities).
The main reason I want to to test refs is because if a ref changes to another element in the component, all the animations that use the component's ref will break. So, I want to specify what elements are accessed when using refs.
Try to use ref.current.getDomNode().

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