Ant Design and React Testing Library - reactjs

I have recently started using Ant Deisgn and really enjoyed working with it.
However I seem to have stumble upon an issue that I am having a hard time solving.
Using react-testing-library for tests, I am having troubles with testing some Ant Design component.
One of the reason is that for some unknown reason some components (e.g. Menu, Menu.Item, Dropdown, etc) do not render the custom attribute data-testid thus making impossible to target a specific element of the DOM.
This makes the tests less performant and accurate.
Did some else stumble upon the same issue ?
How did you go about solving this ?
Is there something that can be done by the Ant Design team about this issue ?

The data-testid attribute is configurable to whatever attribute you want.
https://testing-library.com/docs/dom-testing-library/api-configuration
Also, as long as the library you choose, has ID attribute, you çan do following :
const {container} = render(<Foo />) ;
const button = container.querySelector('#idValue'); // returns react element
fireEvent.click(button);
P. S: mobile edit. Pls ignore formats

Today, I had the same issue with testing a label of an Ant Design Desciptions.Item with the react-testing-library.
I figured out that the label was in a <span> element, so I moved it into a <div> element which solved my problem.
Before:
<Descriptions.Item
data-testid='test-label-of-descriptions-item-failed'
label={'some label'}
labelStyle={{ ...FONT_SMALL }}
contentStyle={{ ...FONT_SMALL }}
style={{ ...DESC_ITEM }}>
After:
<Descriptions.Item
label={ <div style={{ display: 'inline-block' }} data-testid='test-label-of-descriptions-item-worked' >'some label' </div> }
labelStyle={{ ...FONT_SMALL }}
contentStyle={{ ...FONT_SMALL }}
style={{ ...DESC_ITEM }}>

Related

React/Typescript/mui- Help understanding abstraction over a third party component

We started a project and implemented several pages using material ui's React library. We use React hooks exclusively. I was told to create a wrapper around the mui DataGrid. The purpose behind the decision was that a particular page would need functionality that the DataGrid probably doesn't have. A special case, and one which we don't know the specific needs of at this point. I'm a relative newbie with React and Typescript and I don't even know how you go about creating wrappers, especially with a component as large and feature rich as mui's DataGrid. Where to start? I started like this:
import {
DataGrid as DataGridWrapper,
GridColumns,
GridLocaleText,
GridRowsProp,
GridFilterModel,
GridSortModel,
GridCallbackDetails,
} from "#mui/x-data-grid";
import Tooltip from "#mui/material/Tooltip";
import IconButton from "#mui/material/IconButton";
import AddBoxIcon from "#mui/icons-material/AddBox";
import { esES } from "#mui/x-data-grid";
import { enUS } from "#mui/x-data-grid";
export interface GridProps {
style?: React.CSSProperties;
gridColumns: GridColumns;
gridRows: GridRowsProp;
gridPageSize?: number;
showAddButton?: boolean;
onAddButtonClick?: () => void;
onFilterModelChange?: (model: GridFilterModel, details: GridCallbackDetails) => void;
onSortModelChange?: (model: GridSortModel, details: GridCallbackDetails) => void;
}
function DataGrid({
style,
gridColumns,
gridRows,
gridPageSize = 10,
showAddButton = true,
onAddButtonClick,
onFilterModelChange,
onSortModelChange,
}: GridProps) {
const strings = useLocalizedStrings();
// use text translations from the grid and apply them to all pages
let localeTextTranslations : Partial<GridLocaleText>
if (strings.getLanguage() === "es") {
localeTextTranslations = esES.components.MuiDataGrid.defaultProps.localeText;
} else {
localeTextTranslations = enUS.components.MuiDataGrid.defaultProps.localeText;
}
return (
<>
{showAddButton && (
<div style={{ textAlign: "right" }}>
<Tooltip title="Add">
<IconButton
aria-label="add"
color="primary"
onClick={() => {
onAddButtonClick?.()
}}
>
<AddBoxIcon />
</IconButton>
</Tooltip>
</div>
)}
<div style={{ height: 700, width: "100%" }}>
<div style={{ display: "flex", height: "100%" }}>
<div style={{ flexGrow: 1 }}>
<DataGridWrapper
columns={gridColumns}
rows={gridRows}
style={style}
hideFooterSelectedRowCount={true}
pageSize={gridPageSize}
onFilterModelChange={onFilterModelChange}
onSortModelChange={onSortModelChange}
/>
</div>
</div>
</div>
</>
);
}
export default DataGrid;
At this point I started wondering what the benefit of doing this is, if I'm simply duplicating the mui interface. Plus, I don't know how to handle the typings. Do I create new types that wrap mui's types? That seems like it could get complicated pretty fast as everything in mui has underlying types and so do those types, many levels deep. Or should consumers import the types from mui directly like I have here where I'm typing my props as mui types (but that seems wrong and against the idea of abstraction).
So if I'm not really encapsulating logic and am instead simply renaming props, does this even make sense to do? How does one approach such a task? In the end, this just feels weird so far. To make all pages use something in basically the same way you would use the actual mui grid. I feel like I'm missing something.
Can someone give me an example of some patterns I would use to make this actually be the start of a proper wrapper? And how to handle the typings required by mui and translating those to what's exposed to consumers? Or any thoughts at all? I searched for creating 3rd party wrapper components in React and only saw stuff with classes and higher order components. Nothing with hooks. In the end, I guess my specific questions are:
how to create a basic wrapper?
how to handle typings of the thing you wrap?
any considerations I should keep in mind?
I almost don't know what I don't know so it's difficult to be specific. I wish I could find some examples.

Adding a class with styling does nothing - NextJS

I am currently trying to learn NextJS and I've decided to go with a simple weather api that collects data from given city. I've learnt some new stuff and I really like it so far. There is however one issue that I can't seem to solve and that would be adding a class with styling.
Now... Given the GIF below, you can see it DOES add the class when location is being typed but the styling does not change.
https://gyazo.com/55be0759cc8cc514905a7a661274f73c
The 'Home_main__1Z1aG' should change its height and font-size when I add the class but it doesnt. I've never experienced this before and I've made sure that I am targeting the correct div.
I use state to add the class to the 'Home_main__1Z1aG' div.
<main className={`${styles.main} ${searchedCity ? 'loc_set' : ''}`}>
<input value={searchedCity} placeholder="City, Country" className={styles.title} onChange={e => setSearchedCity(e.target.value)} />
</main>
My styling:
.loc_set {
height: 10vh;
.title {
font-size: 2.2rem;
}
}
One important thing to mention is that I've added SCSS to my project.
I think that I need to somehow render the whole div since it is serverside rendered, but am I thinking wrong or? I've basically tried moving my styling around to make sure nesting wasnt an issue, I've made sure that the class is correctly named etc..
Components in Next.js use CSS modules. Assuming that the HTML you've provided is in a component and not a page, you'll have to reference it using imports.
<main className={`${styles.main} ${searchedCity ? styles.locSet : ''}`}>
<input value={searchedCity} placeholder="City, Country" className={styles.title} onChange={e => setSearchedCity(e.target.value)} />
</main>
I am not sure if _ would work, so maybe change that to a hyphenated classname.
.loc-set {
height: 10vh;
.title {
font-size: 2.2rem;
}
}
You have to use ampersand:
.loc_set {
height: 10vh;
& .title {
font-size: 2.2rem;
}
}

Difference between the `className` prop and the new MUI system utility `sx` prop?

Ive mostly been using the makeStyles hook to style my material-ui components. It's fairly straightforward and modular and performant.
Material-UI v5 has the new sx prop that seems fairly promising, but towards the bottom of their docs there, they mention that there is a performance tradeoff.
My question is what's the ultimate difference here between makeStyles and sx, and should I consider switching?
I feel like I should also point out that using the sx prop seems to bulk up the return section of the code and using makeStyles just feels cleaner.
Am I missing something here or is there something revolutionary about sx, that should make me want to use it?
As per my understanding SX is the alternative of style.
When it comes to ReactJs, if you try to use inline styles you need to
In HTML:
<div class="awesome" style="background-color: #f00">
<p style="font-size:14px;">
</div>
Converted to JSX:
<div className="awesome" style={{backgroundColor: '#f00'}}>
<p style={{fontSize: '14px'}}></p>
</div>
With SX : It is a super-set, Responsive, themes
<div sx={{ width: { tablet: 100, laptop: 300, desktop: 500, }, }} >
This box has a responsive width
</div>
Don't be in a Hurry to use, as per the benchmark results it is taking lot of time https://next.material-ui.com/system/basics/#performance-tradeoff

How to correctly use Reach Router Links with a Fluent UI Nav

Is there a way to make the Fluent UI (formerly Office Fabric UI) Nav component correctly work replacing it's <a> element links with a custom link such as <Link> from Reach Router?
Fluent UI Nav provides an onRenderLink props but that only changes the inner content innermost <a> element. It leaves the outer wrapping element with a traditional <a> which causes full page re-renders when the user uses it.
It also provides the linkAs prop but that changes the entire content of the "Group Header" and takes a way all of the nice CSS styling advantages of using the Nav in the first place.
Is there a way to actually get this working?
I did a little more googling and found this codepen which uses LinkAs :
(props) => {
return <Link className={props.className} style={{color: 'inherit', boxSizing: 'border-box'}} to={props.href}>
<span style={{display: 'flex'}}>
{ !!props.iconProps && <Icon style={{margin: '0 4px'}} {...props.iconProps} /> }
{props.children}
</span>
</Link> ;
}
A little bit disappointing that you basically have to reverse engineer their CSS and render it yourself in order to get this working.

Can't get Popover to display in correct position in Dialog

I have a Dialog and have a ListItem that when you click on it goes into edit mode by showing a Popover. This was working in an older version of MUI using a Modal but since getting on the latest that didn't work and I'm trying to use a Popover. I tried to make a simple example on CodeSandox but that works. What happens is the Popover is always in the upper left of the page instead of the ListItem.
I have simplified my code to a simple Button and Popover in the Dialog and still have the same problem and have ran out of ideas on what to try next. The error I get in the console is
[Warning] Material-UI: the `anchorEl` prop provided to the component is invalid.
The anchor element should be part of the document layout.
Make sure the element is present in the document or that it's not display none.
When the item is clicked I do event.currentTarget just like in the examples and this is what the console.log looks like for it.
[Log] <button class="MuiButtonBase-root MuiButton-root MuiButton-text" tabindex="0" type="button"> (main.chunk.js, line 26437)
<span class="MuiButton-label">Click Me</span>
<span class="MuiTouchRipple-root">
<span class="MuiTouchRipple-ripple MuiTouchRipple-rippleVisible" style="width: 117.2006825918689px; height: 117.2006825918689px; top: -34.60034129593445px; left: -25.60034129593445px;">
<span class="MuiTouchRipple-child MuiTouchRipple-childLeaving"></span>
</span>
</span>
</button>
I even tried doing disablePortal in the Dialog which didn't fix it. I also tried using refs which fixed the anchorEl warning but still displays relative to the page and not the element. Any ideas?
For anyone that comes across this issue with Material UI, there are a couple of things that you can do.
One is to make sure that if you have multiple nested functional components, that your anchorEl / click handlers for the popover are defined within the specific functional component that holds the popover. If you have nested functional components and the parent component holds the state, it will rerender the children on every state change, which can reset the anchorEl reference.
Second - React.memo can prevent unnecessary rerenders on functional components (it only works if props don't change but should still reap performance benefits in child components).
I have nested elements this is how I solved this without doing anything too extra.
So my main functional component simply returned something like this
const filters = () => {
const [anchorEl, setAnchorEl] = useState(null)
const popoverOpen = Boolean(anchorEl)
const handleTogglePopover = (event: any) => {
event.preventDefault()
if (anchorEl === null) {
setAnchorEl(event.currentTarget)
} else {
setAnchorEl(null)
}
}
const SortActions = () => {
return (
<Box>
<MyCustomRadioButton/>
</Box>
)
}
const FilterButtons = () => {
return (
<Box>
<ButtonBase
onClick={handleTogglePopover}
aria-owns={popoverOpen ? 'my-popover-id-name' : undefined}
aria-haspopup={true}
>
{/* contents (this is a comment in html in react) */}
</ButtonBase>
<Popover
id={'my-popover-id-name'}
open={popoverOpen}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
onClose={handleTogglePopover}
>
<SortActions/>
</Popover>
</Box>
)
}
return (
<Box>
{/* THIS LINE IS THE LINE I CHANGED TO GET IT TO WORK */}
<FilterButtons/>
</Box>
)
}
I changed that line to {FilterButtons()} instead. It looks like the component that is rendering the popover needs to exist within the functional component calling it.
However any nested components under do not need to be declared within the calling functional component.
From what I have gathered in looking at many peoples solutions is to use React.memo for this but this worked for me. Something about React re-rendering the popover losing the state when its called as a nested component rather than a function within the component causes the state to be lost? I assume it has to do with how JavaScript works in terms of encapsulation within a function.
I know this is an older question but I know people will still run by this question eventually.
It's also possible to get this error due to what it's saying - you might be trying to use an element that has display: none style as an achorEl for your component, which isn't supported as the underlying logic calculating the position of the anchor element needs it to be visible on screen.
Check if there is any display: none; style
May be anchorEl used in multiple nested functional components problem
Try to use memo concept to prevent component rerender

Resources