How to test for tooltip title in jest and testing/library - reactjs

I want to test that the tooltip title is equal to a specific text or not.
This is my antd tooltip I want to write a test for that:
<Tooltip
title={
this.props.connection ? "Connected" : "Disconnected (Try again)"
}>
<Badge status="default" data-testid="connection-sign" />
</Tooltip>
and this is my test in jest:
test("Show error title in tooltip", async () => {
baseDom = render(cardComponent);
fireEvent.mouseMove(await baseDom.findByTestId("connection-sign")); //To hover element and show tooltip
expect(
baseDom.getByTitle(
"Disconnected (Try again)"
)
).toBeInTheDocument();
});
but this test failed and unable to find an element with this title. How can I test that my tooltip contain "Disconnected (Try again)"?

There are multiple mistakes in your test.
Passing component type instead of component instance to render
// this is wrong, passing component type
baseDom = render(cardComponent);
// this is right, passing component instance created with JSX
baseDom = render(<cardComponent />);
Using mouseMove instead of mouseOver event
Searching element by title and passing text instead of searching by text
// wrong, because, the prop on the Tooltip is called 'title'
// but it is actually text as 'getByTitle' looks for HTML
// title attribute
baseDom.getByTitle("Disconnected (Try again)");
// right, because you are actually looking for text not
// HTML title attribute (but wrong see (4))
baseDom.getByText("Disconnected (Try again)");
Using sync method for Tooltip search instead of async
// wrong, because, Tooltip is added dynamically to the DOM
baseDom.getByText("Disconnected (Try again)");
// right
await baseDom.findByText("Disconnected (Try again)");
To sum up, with all mistakes fixed the test should look like this:
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import App from "./App";
test("Show error title in tooltip", async () => {
const baseDom = render(<cardComponent />);
fireEvent.mouseOver(baseDom.getByTestId("connection-sign"));
expect(
await baseDom.findByText("Disconnected (Try again)")
).toBeInTheDocument();
});

In addition to the accepted answer, it's important to make sure if you set the prop getPopupContainer for an Antd tooltip, the popup might not be visible to react testing library as it happened in my case since the DOM container set may not be available in the body when testing the component especially if it's a unit test. e.g
In my case I had
<Popover
getPopupContainer={() => document.getElementById('post--component-drawer')}
content={<h1>Hello world</h1>}>
<span data-testid="symbol-input--color-input">Click me</span>
</Popover>
div post--component-drawer was not available for that unit test. So I had to mock Popover to make sure I override prop getPopupContainer to null so that the popup would be visible
So at the beginning of my test file, I mocked Popover
jest.mock('antd', () => {
const antd = jest.requireActual('antd');
/** We need to mock Popover in order to override getPopupContainer to null. getPopContainer
* sets the DOM container and if this prop is set, the popup div may not be available in the body
*/
const Popover = (props) => {
return <antd.Popover {...props} getPopupContainer={null} />;
};
return {
__esModule: true,
...antd,
Popover,
};
});
test('popver works', async () => {
render(<MyComponent/>);
fireEvent.mouseOver(screen.getByTestId('symbol-input--color-input'));
await waitFor(() => {
expect(screen.getByRole('heading', {level: 1})).toBeInTheDocument();
});
});

I was tried many ways but didn't work, therefore I tried
mouse enter instead of mouseOver or mouseMove and it's worked for me.
here is a solution to test tooltip content, like as:
import { render, cleanup, waitFor, fireEvent, screen } from '#testing-library/react';
// Timeline component should render correctly with tool-tip
test('renders TimeLine component with mouse over(tool-tip)', async () => {
const { getByTestId, getByText, getByRole } = render(
<TimeLine
items={timeLineItems()}
currentItem={currentTimeLineItem()}
onTimeLineItemChange={() => {}}
/>
);
const courseTitle = "Course collection-3";
fireEvent.mouseEnter(getByRole('button'));
await waitFor(() => getByText(courseTitle));
expect(screen.getByText(courseTitle)).toBeInTheDocument();
});

I found this to be the most up to date way. You've got to do async() on the test and then await a findByRole since it isn't instantaneous!
render(<LogoBar />);
fireEvent.mouseEnter(screen.getByLabelText('DaLabel'));
await screen.findByRole(/tooltip/);
expect(screen.getByRole(/tooltip/)).toBeInTheDocument();

This is a slight modification to the Akshay Pal's solution:
import { render, cleanup, waitFor, fireEvent, screen } from '#testing-library/react';
// Timeline component should render correctly with tool-tip
test('renders TimeLine component with mouse over(tool-tip)', async () => {
render(
<TimeLine
items={timeLineItems()}
currentItem={currentTimeLineItem()}
onTimeLineItemChange={() => {}}
/>
);
const courseTitle = "Course collection-3";
fireEvent.mouseEnter(screen.getByRole('button'));
expect(await screen.getByText(courseTitle)).toBeTruthy();
});

Related

React - How to get unit test coverage for toggling open a popover that uses state or determine if it should be open

I'm trying to get unit test coverage for the code in red (see screenshot) using react-testing-library. Would anyone know what unit test would cover this? I'm still learning the react-testing-library. TIA
screenshot of code here showing red, uncovered code
If you don't open the screenshot above, the code inside this function is what needs to be covered.
const togglePopover = () => {
setToolTipOpen((prev) => !prev);
};
actual full component code block:
import React, { FunctionComponent, useState, KeyboardEvent, useRef, useEffect } from 'react';
import styles from './InfoPopover.module.scss';
import { Popover, PopoverBody } from 'x'
import { PopperPlacementType } from '#material-ui/core';
import { ReactComponent as InfoIcon } from '../../../assets/icons/tooltipIcon.svg';
export interface PopperProps {
placement?: PopperPlacementType;
tipMessage: React.ReactNode | string;
stringedTipMessage: string;
}
const InfoPopover: FunctionComponent<PopperProps> = ({
placement,
tipMessage,
stringedTipMessage
}: PopperProps) => {
const [toolTipOpen, setToolTipOpen] = useState(false);
const togglePopover = () => {
setToolTipOpen((prev) => !prev);
};
const handleBlur = () => {
setToolTipOpen(false);
};
return (
<>
<button
id="popoverTarget"
className={styles.tooltipButton}
onBlur={handleBlur}
aria-label={`Tooltip Content - ${stringedTipMessage}`}
>
<InfoIcon aria-label="status tooltip" />
</button>
<Popover
target="popoverTarget"
trigger="legacy"
toggle={togglePopover}
placement={placement}
isOpen={toolTipOpen}
arrowClassName={styles.toolTipArrow}
popperClassName={styles.toolTipPopout}
>
<PopoverBody>{tipMessage}</PopoverBody>
</Popover>
</>
);
};
export default InfoPopover;
With React Testing Library, the approach is to test what the user can see/do rather than test the internals of your application.
With your example, assuming you are trying to test a simple open/close popup user flow then the user would be seeing a button, and when they activate that button they would see a popover. A simple RTL approach would be as follows:
const popoverTipMessage = "My popover message";
render(<InfoPopover tipMessage={popoverTipMessage} />);
// Popover isn't activated, so it shouldn't be in the DOM
expect(screen.getByText(popoverTipMessage)).not.toBeInDocument();
// Find button and click it to show the Popover
fireEvent.click(screen.getByRole('button', {
name: /tooltip content/i
}));
// Popover should now be activated, so check if it's visible (in the DOM)
await waitFor(() => {
// This relies on RTL's text matching to find the component.
expect(screen.getByText(popoverTipMessage)).toBeInDocument();
});
// Find button and click it again to hide the Popover
fireEvent.click(screen.getByRole('button', {
name: /tooltip content/i
}));
// Popover should now be hidden, so check if the DOM element has gone
// Note: There are other ways of checking appearance/disappearance. Check the RTL docs.
await waitFor(() => {
// This relies on RTL's text matching to find the component but there are other better ways to find an element
expect(screen.getByText(popoverTipMessage)).not.toBeInDocument();
});
The query methods I've used above are some of the basic ones, however RTL has many different queries to find the element you need to target. It has accessibility at the forefront of its design so leans heavily on these. Take a look in the docs: https://testing-library.com/docs/react-testing-library/example-intro

Cleanup does not prevent error for finding multiple elements with the same role

I am trying to run a test with the React Testing Library
It clicks a button inside a component. I map over the component, so the button exists several times.
Even though i have a cleanup set up, i still get the following error:
** TestingLibraryElementError: Found multiple elements with the role "button" **
This is my code:
afterEach(cleanup);
describe("button", () => {
it("calls a function when button is clicked", () => {
const callback = jest.fn();
render(<ProductCard onCartToggleClicked={callback} />);
const { getByRole } = render(<ProductCard />);
fireEvent.click(getByRole("button"));
expect(callback).toHaveBeenCalled();
});
afterEach(cleanup);
});
Since you have multiple buttons in the ProductCard component . You should be using getAllByRole instead of getByRole .
Now you can fireEvent with the index as
fireEvent.click(getByRole("button")[0]);
But if you want to target the button specifically then i would suggest to add data-testid prop to the button.
{someData.map((product, index) => (
<button data-testid={`button-${index}`}> click </button>
))}
Now with this in place you can use the getByTestId query
fireEvent.click(getByTestId("button-0"));
logRoles
If you are not sure how many buttons are present . Then you can use the logRoles .
import { logRoles } from '#testing-library/dom'
const { container } = render(<ProductCard onCartToggleClicked={callback} />)
logRoles(container)
This will give you all the possible roles.

Testing click event in React Testing Library

Here is a simple subcomponent that reveals an answer to a question when the button is clicked:
const Question = ({ question, answer }) => {
const [showAnswer, setShowAnswer] = useState(false)
return (
<>
<article>
<header>
<h2 data-testid="question">{question}</h2>
<button onClick={() => setShowAnswer(!showAnswer)}>
{
!showAnswer ? <FiPlusCircle /> : <FiMinusCircle />
}
</button>
</header>
{
showAnswer && <p data-testid="answer">{answer}</p>
}
</article>
</>
)
}
export default Question;
I am trying to test that when the button is clicked, the onClick attached is called once and the a <p> element appears on the screen:
const onClick = jest.fn()
test('clicking the button toggles an answer on/off', () => {
render(<Question />);
const button = screen.getByRole('button')
fireEvent.click(button)
expect(onClick).toHaveBeenCalledTimes(1);
expect(screen.getByTestId('answer')).toBeInTheDocument()
fireEvent.click(button)
expect(screen.getByTestId('answer')).not.toBeInTheDocument()
screen.debug()
})
RTL says that onClick is not called at all (in the UI it is, as the result is as expected)
Also, if I want to test that this button really toggles the answer element (message should come on and off) how would I test for that?
If I add another fireEvent.click() to the test (simulating the second click on the button which should trigger the answer element off), and add
expect(screen.getByTestId('answer')).not.toBeInTheDocument()
RTL will just not find that element (which is good, I guess, it means it has been really toggled off the DOM). What assertion would you use for this test to pass for that case?
Couple of issues with your approach.
First, creating an onClick mock like that won't mock your button's onClick callback. The callback is internal to the component and you don't have access to it from the test. What you could do instead is test the result of triggering the onClick event, which in this case means verifying that <FiMinusCircle /> is rendered instead of <FiPlusCircle />.
Second, p is not a valid role - RTL tells you which roles are available in the DOM if it fails to find the one you searched for. The paragraph element doesn't have an inherent accessibility role, so you're better off accessing it by its content with getByText instead.
Here's an updated version of the test:
test('clicking the button toggles an answer on/off', () => {
render(<Question question="Is RTL great?" answer="Yes, it is." />);
const button = screen.getByRole('button')
fireEvent.click(button)
// Here you'd want to test if `<FiMinusCircle />` is rendered.
expect(/* something from FiMinusCircle */).toBeInTheDocument()
expect(screen.getByText('Yes, it is.')).toBeInTheDocument()
fireEvent.click(button)
// Here you'd want to test if `<FiPlusCircle />` is rendered.
expect(/* something from FiPlusCircle */).toBeInTheDocument();
expect(screen.queryByText('Yes, it is.')).not.toBeInTheDocument()
})
In my case this worked:
it('Does click event', () => {
const { container } = render(<Component />);
fireEvent.click(container.querySelector('.your-btn-classname'));
// click evt was triggered
});

Check that button is disabled in react-testing-library

I have a React component that generates a button whose content contains a <span> element like this one:
function Click(props) {
return (
<button disable={props.disable}>
<span>Click me</span>
</button>
);
}
I want to test the logic of this component with the use of react-testing-library and mocha + chai.
The problem at which I stuck at the moment is that the getByText("Click me") selector returns the <span> DOM node, but for the tests, I need to check the disable attribute of the <button> node. What is the best practice for handling such test cases? I see a couple of solutions, but all of them sound a little bit off:
Use data-test-id for <button> element
Select one of the ancestors of the <Click /> component and then select the button within(...) this scope
Click on the selected element with fireEvent and check that nothing has happened
Can you suggest a better approach?
Assert if button is disabled
You can use the toHaveAttribute and closest to test it.
import { render } from '#testing-library/react';
const { getByText } = render(Click);
expect(getByText(/Click me/i).closest('button')).toHaveAttribute('disabled');
or toBeDisabled
expect(getByText(/Click me/i).closest('button')).toBeDisabled();
Assert if button is enabled
To check if the button is enabled, use not as follows
expect(getByText(/Click me/i).closest('button')).not.toBeDisabled();
You can use toBeDisabled() from #testing-library/jest-dom, it is a custom jest matcher to test the state of the DOM:
https://github.com/testing-library/jest-dom
Example:
<button>Submit</button>
expect(getByText(/submit/i)).toBeDisabled()
For someone who is looking for the test in which the button is not disabled.
import { render } from '#testing-library/react';
const { getByText } = render(Click);
expect(getByText(/Click me/i).getAttribute("disabled")).toBe(null)
I would politely argue you are testing an implementation detail, which react-testing-library discourages.
The more your tests resemble the way your software is used, the more confidence they can give you.
If a button is disabled, a user doesn't see a disabled prop, instead they see nothing happen. If a button is enabled, a user doesn't see the omission of a disabled prop, instead they see something happen.
I believe you should be testing for this instead:
const Button = (props) => (
<button
type="submit"
onClick={props.onClick}
disabled={props.disabled}
>
Click me
</button>
);
describe('Button', () => {
it('will call onClick when enabled', () => {
const onClick = jest.fn();
render(<Button onClick={onClick} disabled={false} />);
userEvent.click(getByRole('button', /click me/i));
expect(onClick).toHaveBeenCalledTimes(1);
});
it('will not call onClick when disabled', () => {
const onClick = jest.fn();
render(<Button onClick={onClick} disabled={true} />);
userEvent.click(getByRole('button', /click me/i));
expect(onClick).not.toHaveBeenCalled();
});
})
toHaveAttribute is good option in using attribute.
<button data-testid="ok-button" type="submit" disabled>ok</button>
const button = getByTestId('ok-button')
//const button = getByRole('button');
expect(button).toHaveAttribute('disabled')
expect(button).toHaveAttribute('type', 'submit')
expect(button).not.toHaveAttribute('type', 'button')
expect(button).toHaveAttribute('type', expect.stringContaining('sub'))
expect(button).toHaveAttribute('type', expect.not.stringContaining('but'))
Hope this will be helpful.
You can test the disable prop of the button just by using #testing-library/react as follows.
example:
import { render } from '#testing-library/react';
const {getByText} = render(<Click/>)
expect(getByText('Click me').closest('button').disabled).toBeTruthy()
Another way to fix this would be to grab by the role and check the innerHTML like,
const { getByRole } = render(<Click />)
const button = getByRole('button')
// will make sure the 'Click me' text is in there somewhere
expect(button.innerHTML).toMatch(/Click me/))
This isn't the best solution for your specific case, but it's one to keep in your back pocket if you have to deal with a button component that's not an actual button, e.g.,
<div role="button"><span>Click Me</span></div>
My solution, It seems to me that this case covers well what is necessary. Check that the button is disabled, so toHaveBeenCalledTimes must receive 0
test('Will not call onClick when disabled', () => {
const mockHandler = jest.fn()
render(<Button title="Disabled account" disabled={true} onClick={mockHandler} />)
const button = screen.getByText("Disabled account")
fireEvent.click(button)
expect(mockHandler).toHaveBeenCalledTimes(0)
expect(button).toHaveProperty('disabled', true)
})

React testing library: Test styles (specifically background image)

I'm building a React app with TypeScript. I do my component tests with react-testing-library.
I'm buildilng a parallax component for my landing page.
The component is passed the image via props and sets it via JSS as a background image:
<div
className={parallaxClasses}
style={{
backgroundImage: "url(" + image + ")",
...this.state
}}
>
{children}
</div>
Here is the unit test that I wrote:
import React from "react";
import { cleanup, render } from "react-testing-library";
import Parallax, { OwnProps } from "./Parallax";
afterEach(cleanup);
const createTestProps = (props?: object): OwnProps => ({
children: null,
filter: "primary",
image: require("../../assets/images/bridge.jpg"),
...props
});
describe("Parallax", () => {
const props = createTestProps();
const { getByText } = render(<Parallax {...props} />);
describe("rendering", () => {
test("it renders the image", () => {
expect(getByText(props.image)).toBeDefined();
});
});
});
But it fails saying:
● Parallax › rendering › it renders the image
Unable to find an element with the text: bridge.jpg. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
<body>
<div>
<div
class="Parallax-parallax-3 Parallax-primaryColor-4"
style="background-image: url(bridge.jpg); transform: translate3d(0,0px,0);"
/>
</div>
</body>
16 | describe("rendering", () => {
17 | test("it renders the image", () => {
> 18 | expect(getByText(props.image)).toBeDefined();
| ^
19 | });
20 | });
21 | });
at getElementError (node_modules/dom-testing-library/dist/query-helpers.js:30:10)
at getAllByText (node_modules/dom-testing-library/dist/queries.js:336:45)
at firstResultOrNull (node_modules/dom-testing-library/dist/query-helpers.js:38:30)
at getByText (node_modules/dom-testing-library/dist/queries.js:346:42)
at Object.getByText (src/components/Parallax/Parallax.test.tsx:18:14)
How can I test that the image is being set as a background image correctly with Jest and react-testing-library?
getByText won't find the image or its CSS. What it does is to look for a DOM node with the text you specified.
In your case, I would add a data-testid parameter to your background (<div data-testid="background">) and find the component using getByTestId.
After that you can test like this:
expect(getByTestId('background')).toHaveStyle(`background-image: url(${props.image})`)
Make sure you install #testing-library/jest-dom in order to have toHaveStyle.
If you want to avoid adding data-testid to your component, you can use container from react-testing-library.
const {container} = render(<Parallax {...props})/>
expect(container.firstChild).toHaveStyle(`background-image: url(${props.image})`)
This solution makes sense for your component test, since you are testing the background-image of the root node. However, keep in mind this note from the docs:
If you find yourself using container to query for rendered elements then you should reconsider! The other queries are designed to be more resiliant to changes that will be made to the component you're testing. Avoid using container to query for elements!
in addition to toHaveStyle JsDOM Matcher, you can use also style property which is available to the current dom element
Element DOM API
expect(getByTestId('background').style.backgroundImage).toEqual(`url(${props.image})`)
also, you can use another jestDOM matcher
toHaveAttribute Matcher
expect(getByTestId('background')).toHaveAttribute('style',`background-image: url(${props.image})`)
Simple solution for testing Component css with react-testing-library. this is helpful for me it is working perfect.
test('Should attach background color if user
provide color from props', () => {
render(<ProfilePic name="Any Name" color="red" data-
testid="profile"/>);
//or can fetch the specific element from the component
const profile = screen.getByTestId('profile');
const profilePicElem = document.getElementsByClassName(
profile.className,
);
const style = window.getComputedStyle(profilePicElem[0]);
//Assertion
expect(style.backgroundColor).toBe('red');
});

Resources