Using react-testing-library, I wish to test a form implemented in React.
That form includes a React component of type react-select.
It is necessary to click a part of the react-select component that has no label, no text, etc. (E.g. the dropdown arrow).
Ordinarily, the react-testing-library way to do this is to add a 'data-testid' attribute to the item in question.
I've found that it's possible to give each part of the react-select a CSS class attribute, by providing the 'classNamePrefix' prop to the react-select component. Is there some way to do the same for data-testid attribute?
Note: I'm aware I can provide custom implementations of the components of react-select, but that seems like overkill to get one attribute in place.
First of all I'd question why there's no label on the Select as this wouldn't be classed as accessible for screen readers.
But, If you don't want a visible label you could always pass an aria-label prop to the Select and test it that way using getByLabelText.
<Select aria-label="Example Label" ... />
getByLabelText('Example Label')
If you really need to add a data-testid you could replace the specific components you want to add the data-testid too and add it. (See the docs for more info)
e.g.
// #flow
import React from 'react';
import EmojiIcon from '#atlaskit/icon/glyph/emoji';
import Select, { components } from 'react-select';
import { colourOptions } from './docs/data';
const DropdownIndicator = props => {
return (
<components.DropdownIndicator {...props}>
<span data-testid="DropdownIndicator">
<EmojiIcon primaryColor={colourOptions[2].color} />
</span>
</components.DropdownIndicator>
);
};
export default () => (
<Select
closeMenuOnSelect={false}
components={{ DropdownIndicator }}
defaultValue={[colourOptions[4], colourOptions[5]]}
isMulti
options={colourOptions}
/>
);
Codesandbox link
Before all react-select is just a select. In testing you should keep eyes in your components. react-select is a component outside your project, the test cases belong to their owner.
So in this cases I recommend to just mock the package in your benefit.
Here is an example of how to mock it:
jest.mock('react-select', () => ({ options, value, onChange }) => {
return (
<select data-testid="react-select-mock" defaultValue={value} onChange={onChange}>
{options.map(({ label, value }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
);
});
Related
How can I test whether or not a React component is visible in the DOM when that component is hidden using a CSS transition with transform: scale(0)?
jest-dom has a .toBeVisible() matcher, but this doesn't work because transform: scale(0) is not one of the supported visible/hidden triggers. Per the docs:
An element is visible if all the following conditions are met:
it is present in the document
it does not have its css property display set to none
it does not have its css property visibility set to either hidden or collapse
it does not have its css property opacity set to 0
its parent element is also visible (and so on up to the top of the DOM tree)
it does not have the hidden attribute
if <details /> it has the open attribute
I am not using the hidden attribute because it interfered with my transition animations. I am using aria-hidden, but that is also not one of the supported triggers.
The simplified version of my component is basically this. I am using Tailwind CSS for the transform and the transition.
import React from "react";
import clsx from "clsx";
const MyComponent = ({isSelected = true, text}) => (
<div
className={clsx(
isSelected ? "transform scale-1" : "transform scale-0",
"transition-all duration-500"
)}
aria-hidden={!isSelected}
>
<span>{text}</span>
</div>
)
I could potentially check for hidden elements with:
toHaveClass("scale-0")
toHaveAttribute("aria-hidden", true)
But unlike toBeVisible, which evaluates the entire parent tree, these matchers only look at the element itself.
If I use getByText from react-testing-library then I am accessing the <span> inside the <div> rather than the <div> which I want to be examining. So this doesn't work:
import React from "react";
import { render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import { MyComponent } from "./MyComponent";
it("is visible when isSelected={true}", () => {
const {getByText} = render(
<MyComponent
isSelected={true}
text="Hello World"
/>
);
expect(getByText("Hello World")).toHaveClass("scale-1");
});
What's the best way to approach this?
I am working on a social media app where i have a container component that has the following structure
<MyContainer>
<SelectionBar/>
{condition? <FlatListA header={header}/> : <FlatListB header={header}/>}
<MyContainer/>
the selection bar has buttons that determine which FlatList to display for the purpose of this question lets say messages FlatList vs posts FlatList
these two FlatLists have different listeners and data so they need to be their own component but they share the same ListHeaderComponent which is a feature similar to snapchat stories
the problem is when the user switches between two FlatLists the stories flicker because the component is re rendered because its two different FlatLists
the header needs to be inside the flatlist as a ListHeaderComponent because when the user scrolls down the stories should not stick to the top
is there any way to prevent this re rendering?
I've tried React.memo but that did not work
You can prevent re-rendering of same component by using React.memo
You can define your header component and pass it as a prop like:
import { memo } from "react";
import FlatListA from "./FlatListA";
import FlatListB from "./FlatListB";
const header = memo((props) => {
console.log("header render");
return <h1>this is header</h1>;
});
export default function App() {
return (
<div className="App">
<FlatListA header={header} />
<FlatListB header={header} />
</div>
);
}
and you can use it in your FlatList components like:
import { useState } from "react";
export default function FlatListA(props) {
console.log("flatlista render");
const [toggle, setToggle] = useState(false);
return (
<div>
<props.header />
FlatlistA {toggle}
<button onClick={() => setToggle(!toggle)}>toogle state</button>
</div>
);
}
You can take a look at this example codesandbox and click buttons to change state and see console outputs that it does not re-render header components.
I'm currently trying to use Nav with react-router. The default behavior reloads the page, so I'm trying to use the Link component from react-router-dom.
It's quite difficult to preserve the default styling when overriding linkAs.
Is there any global way to override link navigation behavior?
Like defining a global link render function, which I can then set to render the Link component from react-router-dom?
Yes, it's possible!
2 things are required:
Make a wrapper component that translates the Nav API to react-router-dom links.
Specify the linkAs prop to the Nav component.
Wrapper component
This is a simple component that creates a react-router-dom link while using styles from Fabric:
import { Link } from "react-router-dom";
const LinkTo = props => {
return (
<Link to={props.href} className={props.className} style={props.style}>
{props.children}
</Link>
);
};
Specify component for use in Nav
<Nav groups={links} linkAs={LinkTo} />
Have also created a full working example at https://codesandbox.io/s/xenodochial-wozniak-y10tr?file=/src/index.tsx:605-644
In React, I would often construct HOCs which would wrap a component with some bit of markup. For example,
const WithLabel = WrappedComponent => ({ label, ...props }) => (
<label>
<div>{label}</div>
<WrappedComponent {...props }/>
</label>
);
Is there an equivalent construct for this sort of thing in Vue? I've read the documentation on mixins and slots and have seen many examples and read articles on how others are creating HOCs in Vue. But none of the examples or libraries I've seen seem to be able to handle markup decoration.
I'm using the react-select control. The items displayed in the dropdown list are a combination of fixed items plus dynamically added items.
I'd like to be able to delete the dynamically generated items directly in the dropdown panel by adding an icon next to the label. When clicked this should remove the item.
I know the code to add/remove items programmatically. It's just a case of updating state. The thing I'm stuck on is how to add UI to the react-select dropdown panel and fire a click event when it's clicked on.
According to the docs you can replace the option component in react-select.
import React from 'react';
import Select from 'react-select';
const CustomOption = ({ innerProps }) =>
<div {...innerProps}>{/* your component internals */}</div>
class Component extends React.Component {
render() {
return <Select components={{ Option: CustomOption }} />;
}
}
That way you could add an icon <span onClick={() => this.deleteOption(optionId)}>×</span> to the CustomOption component and use css position: absolute etc. to get it where you want and style it, preferably through a className