React testing library unable to detect datagrid row cells - reactjs

Current behavior/issue:
Using react testing libraries queries, I am not able to detect row cells or the data inside them, even after implementing the correct queries for data not available immediately.
Expected behavior:
Using forBy queries should result in a passing testing and show those rendered rows in screen.debug.
Code/Steps to reproduce:
import { render, screen } from '#testing-library/react';
import PerformanceDataGridModal from '../../features/PerformanceDataGridModal/PerformanceDataGridModal';
import ChartMockData from '../../utils/Mocks/ChartsMockData/ChartMockData';
import '#testing-library/jest-dom';
describe('Performance datagrid modal', () => {
test.each(ChartMockData)('opens with correct information based on %s button click', async (item) => {
const CloseDataGrid = jest.fn();
const ClosedDataGridModal = (
<PerformanceDataGridModal
open={false}
onclose={CloseDataGrid}
rows={ChartMockData[item.id].rows}
columns={ChartMockData[item.id].columns}
/>
);
const OpenedDataGridModal = (
<PerformanceDataGridModal
open
onclose={CloseDataGrid}
rows={ChartMockData[item.id].rows}
columns={ChartMockData[item.id].columns}
/>
);
render(ClosedDataGridModal);
expect(screen.queryByRole('dialog')).toBeFalsy();
expect(screen.queryByRole('grid')).toBeFalsy();
expect(screen.queryByRole('cell', { name: 'Yes' })).toBeFalsy();
render(OpenedDataGridModal);
expect(screen.getByRole('dialog')).toBeTruthy();
expect(screen.getByRole('grid')).toBeTruthy();
expect(await screen.findByRole('cell', { name: 'jane' })).toBeTruthy();
screen.debug();
});
});
As you see on this line
expect(await screen.findByRole('cell', { name: 'jane' })).toBeTruthy();
I have followed the instructions of react testing library as indicated here:
https://testing-library.com/docs/guide-disappearance
What I've tried?
Await WaitFor, with getBy queries instead of forBy queries
using disableVirtualization as indicated in material mui datagrid section and the following source: https://github.com/mui/mui-x/issues/1151
using jest.requierActual datagrid and setting autoHeight
awaiting the render of component
await the entire assertion(expect....

Related

After first load of Highcharts, how to properly swap different series in/out using React and properly render Highchart?

Currently, I am using useState to swap out the series with a different kind of data set. The goal is to have the user click a button to show/swap the kind of data they want to see. I have attached a codesandbox for demo purposes (and it also shows one of the bugs). When I do it this way (as shown in this sandbox), 2 bugs occur.
Firstly, after swapping the data set, the legend shows series names properly (e.g. per the sandbox, will swap to a single series name as there is technically only 1 series to show along with its corresponding arearange). But after swapping back to the original, the legend only shows one name (of the two available series). However, the line series on the graph itself still shows the 2 lines properly despite the legend only have 1 of the line's names. This behavior shows with more than two series as well.
Secondly, I can't seem to reproduce it in the code sandbox. But essentially with a similar behavior as the first bug I explained earlier, the line series/arearange series will sometimes change. Meaning that when I flip between the data, the line series sometimes becomes an arearange (not intended) and vice versa.
The actual goal of my code is that the chart will get the data as a prop and then I would manipulate this data and set it to state (e.g. filtering an array for specific series sets). I'm not sure why this is not working as intended and any help is much appreciated :).
Here's an example code (same as the sandbox):
import Highcharts, { Options } from "highcharts";
import HighchartsReact from "highcharts-react-official";
import HighchartsExport from "highcharts/modules/exporting";
import HighchartsMore from "highcharts/highcharts-more";
import * as React from "react";
import { LineData, LineDataWithRange } from "./mocks/LineChartMockData";
if (typeof Highcharts === "object") {
HighchartsExport(Highcharts);
HighchartsMore(Highcharts);
}
export const Example: React.FC = () => {
const [chartData, setChartData] = React.useState(LineData);
const handleChangeToDataRange = () => {
setChartData(LineDataWithRange);
};
const handleChangeToJustData = () => {
setChartData(LineData);
};
const chartOptions: Options = {
chart: {
type: "line"
},
title: {
text: "Highcharts/React/Typescript"
},
series: chartData
};
return (
<>
<HighchartsReact highcharts={Highcharts} options={chartOptions} />
<button onClick={handleChangeToDataRange}>
Change to Line Data with Range
</button>
<button onClick={handleChangeToJustData}>Change to Just Line Data</button>
</>
);
};
That's because the wrapper uses chart.update to react to a component update. Series are updated and their options are merged. Example in pure JS: http://jsfiddle.net/gk06rxqw/
As a solution enable immutable property:
<HighchartsReact immutable={true} highcharts={Highcharts} options={chartOptions} />
Or overwrite all of the used series options:
export const LineData = [
{...},
{
...,
linkedTo: null
}
];
export const LineDataWithRange = [
{...},
{
...,
linkedTo: "series3"
}
];
Live example: https://codesandbox.io/s/sample-highchart-forke-o2byno
API Reference: https://api.highcharts.com/class-reference/Highcharts.Chart#update
Docs: https://www.npmjs.com/package/highcharts-react-official#options-details

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

Custom react-admin drag & drop list

It can't drag. What is wrong with it?
I'm using react-sortable-hoc with material-ui to custom react-admin list page with drag & drop sortable.
Demo : https://codesandbox.io/s/vibrant-visvesvaraya-4k3gs
Source code: https://github.com/tangbearrrr/poc-ra-sort-drag/tree/main
As I checked you are getting data from the props and in props there is no data field exists, so the error is coming from there
Here is the all props list
The sortable method you are using is from react-sortable-hoc, which adds huge complexity to react-admin.
Not so fast, I have run out of attempts trying to debug your code and come up with another solution works just fine but not so ideal, is to use sortablejs:
yarn add sortablejs
yarn add #types/sortablejs --dev
Do not mess up with react-sortablejs, this also applies the same complexity level as react-sortable-hoc.
Let's use your cmsLanguage as an example, with changes to use Datagrid instead.
Just be reminded that this working solution needs several retries on null el (e.g. your data is fetching, slow network speed, etc). The code below has 3 retries, 1500 milliseconds per each retry. The initialisation will stop after 3 attempts.
import {Datagrid, ShowButton, TextField} from "react-admin";
import * as React from "react";
import MenuIcon from '#mui/icons-material/Menu';
import {useEffect} from "react";
import Sortable from 'sortablejs';
const LanguageList = () => {
// This will run the effect after every render
useEffect(() => {
// https://github.com/SortableJS/Sortable
const retries = 3;
const millisecords = 1500;
let attempts = 0;
const retrySortable = () => {
const el = document.querySelector('#sortable-list tbody');
if (!el) {
if (++attempts >= retries) {
console.log(`cannot initialise sortable after ${retries} retries.`);
} else {
setTimeout(() => retrySortable(), millisecords);
}
} else {
// #ts-ignore
new Sortable(el, {
handle: ".handle",
draggable: "tr",
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
easing: "cubic-bezier(1, 0, 0, 1)", // Easing for animation. Defaults to null. See https://easings.net/ for examples.
// Element dragging ended
onEnd: (evt) => {
// #ts-ignore
const reorderedList: string[] = [];
const list = document.querySelectorAll('#sortable-list tbody td.column-name span');
[].forEach.call(list, function (span: Element) {
reorderedList.push(span.innerHTML);
});
console.log(JSON.stringify(reorderedList));
console.log(evt);
},
});
}
}
retrySortable();
}, []);
return (
<section id="sortable-list">
<Datagrid>
<MenuIcon sx={{cursor: "pointer"}} className="handle"/>
<TextField source="name"/>
<ShowButton/>
</Datagrid>
</section>
);
};
export default LanguageList;
When someone has a request for a demo, I will draw some time to make this a GitHub repo for better reference.

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

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

How do I trigger the change event on a react-select component with react-testing-library?

Given that I can't test internals directly with react-testing-library, how would I go about testing a component that uses react-select? For instance, if I have a conditional render based on the value of the react-select, which doesn't render a traditional <select/>, can I still trigger the change?
import React, { useState } from "react";
import Select from "react-select";
const options = [
{ value: "First", label: "First" },
{ value: "Second", label: "Second" },
{ value: "Third", label: "Third" },
];
function TestApp() {
const [option, setOption] = useState(null);
return (
<div>
<label htmlFor="option-select">Select Option</label>
<Select
value={option}
options={options}
onChange={option => setOption(option)}
/>
{option && <div>{option.label}</div>}
</div>
);
}
export default TestApp;
I'm not even sure what I should query for. Is it the hidden input?
My team has a test utility in our project that lets us select an item easily after spending too much time trying to figure out how to do this properly. Sharing it here to hopefully help others.
This doesn't rely on any React Select internals or mocking but does require you to have set up a <label> which has a for linking to the React Select input. It uses the label to select a given choice value just like a user would on the real page.
const KEY_DOWN = 40
// Select an item from a React Select dropdown given a label and
// choice label you wish to pick.
export async function selectItem(
container: HTMLElement,
label: string,
choice: string
): Promise<void> {
// Focus and enable the dropdown of options.
fireEvent.focus(getByLabelText(container, label))
fireEvent.keyDown(getByLabelText(container, label), {
keyCode: KEY_DOWN,
})
// Wait for the dropdown of options to be drawn.
await findByText(container, choice)
// Select the item we care about.
fireEvent.click(getByText(container, choice))
// Wait for your choice to be set as the input value.
await findByDisplayValue(container, choice)
}
It can be used like this:
it('selects an item', async () => {
const { container } = render(<MyComponent/>)
await selectItem(container, 'My label', 'value')
})
You can try the following to get it working:
Fire focus event on the ReactSelect component .react-select input element.
Fire a mouseDown event on the .react-select__control element
Fire a click on the option element that you want to select
You can add a className and classNamePrefix props with the value of "react-select" in order to specifically select the component you are trying to test.
PS: In case you are still stuck I'd encourage you to take a look at this conversation from where the above answer is borrowed - https://spectrum.chat/react-testing-library/general/testing-react-select~5857bb70-b3b9-41a7-9991-83f782377581

Resources