How to search by data-test? (not data-testid) - react-testing-library

When I import screen object like this
import { render, screen } from '#testing-library/react';
it allows me to issue the following command: screen.findByTestId(...), but how can I search by data-test (not data-testid)? Tried to search by custom attribute, but there was no findByAttribute method in screen either.

Have you had a chance to see the the document?
https://testing-library.com/docs/dom-testing-library/api-custom-queries/
If you want to query with your own-defined attribute, you can make one using buildQueries
// custom-queries.js
import {queryHelpers, buildQueries} from '#testing-library/react'
// query
const queryAllByData = (...args) =>
queryHelpers.queryAllByAttribute('data-test', ...args)
const [
queryByDataTest,
] = buildQueries(queryAllByData)
export {
queryByDataTest
}
// test-utils.js
import {render, queries} from '#testing-library/react'
import * as customQueries from './custom-queries'
const customRender = (ui, options) =>
render(ui, {queries: {...queries, ...customQueries}, ...options})
// re-export everything
export * from '#testing-library/react'
// override render method
export {customRender as render}
//test.spec.jsx
const {getByData} = render(<Component />)
expect(getByData('my-component')).toHaveTextContent('Hello')
You can find more in the attached document.

Related

Is there a useState concept so I can create a service which holds data that is accessed in multiple components?

I have 2 components who want to access the same data. Instead of each doing an HTTP Request independantly, I wanted to keep the items in parity. When doing react, we can easily do: const [ data, setData ] = useState(undefined) which will allow us to use data in our app and setData to change the global.
I was trying to think of how this might be doable in ReactScala, and Was thinking that there could be some overlap here since you can do something like:
useState[A]( data: A ): Pair[A, A=>A] = {
val d = data
return d, x => {
d = x
return d
}
}
or similar.
I have not seen the documentation on useState in Japgolly as much as defining the property in the component state and then using the state.copy() function to update the value.
The issue which occurred is that to me, state.copy is just 1 component, and wanted to know if there was a way to genericize.
https://github.com/japgolly/scalajs-react/blob/master/doc/HOOKS.md
Under the HOOKS file linked above, the top example shows how useState is translated. I will add it below in case the file is changed or deleted:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [fruit, setFruit] = useState("banana");
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<p>Your favourite fruit is a {fruit}!</p>
</div>
);
}
Compared to:
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import org.scalajs.dom.document
object Example {
val Component = ScalaFnComponent.withHooks[Unit]
.useState(0)
.useEffectBy((props, count) => Callback {
document.title = s"You clicked ${count.value} times"
})
.useState("banana")
.render((props, count, fruit) =>
<.div(
<.p(s"You clicked ${count.value} times"),
<.button(
^.onClick --> count.modState(_ + 1),
"Click me"
),
<.p(s"Your favourite fruit is a ${fruit.value}!")
)
)
}

How to import Lottie component?

Remix is prone to the following error when using import on top-level components TypeError: Cannot read properties of undefined (reading 'root').
So I've done as they recommend and have the following imports.server.tsx file.
export * from "lottie-react";
Then my component app.tsx looks exactly like this lottie example.
import React from "react";
import * as Lottie from "../imports.server";
import groovyWalkAnimation from "../../public/assets/102875-cinema-clap.json";
export default function App() {
return (
<>
<h1>lottie-react - Component</h1>
<Lottie animationData={groovyWalkAnimation} />;
</>
);
}
but I get the following error
JSX element type 'Lottie' does not have any construct or call
signatures.ts(2604)
Edit 1:
The following seems to have worked for imports:
imports.server.tsx
import Lottie from "lottie-react";
export default Lottie;
AppTry.tsx
import React from "react";
import Lottie from "../imports.server";
import groovyWalkAnimation from "../../public/assets/102875-cinema-clap.json";
export default function AppTry() {
// console.log(LottieModule);
return (
<>
<h1>lottie-react - Component</h1>
<Lottie animationData={groovyWalkAnimation}></Lottie>
</>
);
}
Now the various paramaters like "animationData" and "autoPlay" pop up on the Lottie component which I assume means the import is working? However I am now getting this error when rendering AppTry.tsx?
react.development.js:220 Warning: React.createElement: type is invalid
-- expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to
export your component from the file it's defined in, or you might have
mixed up default and named imports.
Check the render method of AppTry.
Edit 2:
import { useLottie } from "lottie-react";
import Lottie from "lottie-react";
import groovyWalkAnimation from "../../public/assets/102875-cinema-clap.json";
const Example = () => {
const options = {
animationData: groovyWalkAnimation,
loop: true,
autoplay: true,
};
const { View } = useLottie(options);
return View;
};
const Example1 = () => {
return <Lottie animationData={groovyWalkAnimation} />;
};
export const TopicOverview = () => {
return (
<div className="space-y-20">
<Example1></Example1>
<Example></Example>
</div>
);
};
Looks like it has to do with your way of importing Lottie.
Shouldn't you import Lottie like this?:
import Lottie from "lottie-react";
I also struggled to get this working in Remix.
You can do the lazy load import somewhere higher up in the tree too.
import type { LottiePlayer } from "#lottiefiles/lottie-player";
import { useEffect } from "react";
interface LottieCompProps {
src: LottiePlayer["src"];
style?: Partial<LottiePlayer["style"]>;
}
function LottieComp({ src, style = {} }: LottieCompProps): JSX.Element | null {
// NB: otherwise, will cause app to crash. see https://remix.run/docs/en/v1/guides/constraints#third-party-module-side-effects
useEffect(() => {
import("#lottiefiles/lottie-player");
},[]);
if (typeof document === "undefined") return null;
return (
//#ts-expect-error dynamic import
<lottie-player
autoplay
loop
mode="normal"
src={typeof src === "string" ? src : JSON.stringify(src)}
style={{
...{
width: "100%",
backgroundColor: "transparent",
},
...style,
}}
/>
);
}
export default LottieComp;
The issue was in my root.tsx, an ErrorBoundary() function that called an <UnexpectedErrors/> component.
This same component was being called in various slug.tsx files. For some reason remix did not like this.
Having two different <UnexpectedErrors/> and <UnexpectedErrors2/> components - one for the slug.tsx files and one for the index.tsx files fixed this.

Map Size when Testing React Leaflet

I'm trying to test a custom react-leaflet component. It grabs the map element and uses a filter to get the number of elements visible at the current zoom level:
const visible = allPoints.filter(p => map.getBounds().contains(p));
This code works fine in the application, but when I try to test with Jest & Enzyme, the filter always returns 0 elements, regardless of zoom level. Poking at it a bit, I discovered that map.getSize() always returns 0x0 and map.getBounds() returns the same point for the southwest and northeast corners, which explains why it can't really contain any other points.
Is this just a bug in Enzyme or Leaflet or is there something that I need to do to set the map size explicitly?
import React from "react";
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import { MapContext } from "../../../context";
import MarkersShown from "../markers-shown";
import { Map } from "react-leaflet";
import { mockParsed, mockCenter, mockData } from "./__data__";
describe("MarkersShown Tests", () => {
it("renders the total asset count", async () => {
const parsed = mockParsed;
const center = mockCenter;
let wrapper;
await act(async () => {
wrapper = mount(
<MapContext.Provider value={mockData}>
<Map zoom={parsed.z} center={center} style={{ height: "100px", width: "100px" }}>
<MarkersShown />
</Map>
</MapContext.Provider>
);
});
wrapper.update();
expect(wrapper.html()).toEqual(
expect.stringMatching("Markers Shown of 3")
);
});
});

Adding Material UI to React SSR

I'm using this really awesome boilerplate for a React SSR app and am trying to add Material UI for css lib. I've gone through their guide for Server Side Rendering but seem to be doing something wrong becuase I can get the button to render as shown here, however there is NO styling applied to the button :((
This is what I've done so far:
In client.js
// added for Material UI
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/core/styles';
import theme from './theme/sitetheme';
...
const render = (Routes: Array) => {
const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate;
React.useEffect(() => {
const jssStyles = document.getElementById('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []),
renderMethod(
<ThemeProvider theme={theme}>
<CssBaseline />
<AppContainer>
<Provider store={store}>
<ConnectedRouter history={history}>
{renderRoutes(Routes)}
</ConnectedRouter>
</Provider>
</AppContainer>
</ThemeProvider>,
// $FlowFixMe: isn't an issue
document.getElementById('react-view')
);
};
And then in server.js
// added for Material UI
import CssBaseline from '#material-ui/core/CssBaseline';
import { ServerStyleSheets, ThemeProvider } from '#material-ui/core/styles';
import theme from './theme/sitetheme';
...
const extractor = new ChunkExtractor({ statsFile });
const sheets = new ServerStyleSheets(); // added for material-ui
const staticContext = {};
const AppComponent = (
sheets.collect(
{/* Setup React-Router server-side rendering */}
{renderRoutes(routes)}
));
const css = sheets.toString(); // for material ui
const initialState = store.getState();
const htmlContent = renderToString(AppComponent);
// head must be placed after "renderToString"
// see: https://github.com/nfl/react-helmet#server-usage
const head = Helmet.renderStatic();
// Check if the render result contains a redirect, if so we need to set
// the specific status and redirect header and end the response
if (staticContext.url) {
res.status(301).setHeader('Location', staticContext.url);
res.end();
return;
}
// Check page status
const status = staticContext.status === '404' ? 404 : 200;
// Pass the route and initial state into html template
res
.status(status)
.send(renderHtml(head, extractor, htmlContent, initialState, css));
and finally in renderHtml.js
export default (
head: Object,
extractor: Function,
htmlContent: string,
initialState: Object,
css: string // added for Material UI
...
${extractor.getLinkTags()}
${extractor.getStyleTags()}
<style id="jss-server-side">${css}</style>
I'm not exactly sure what I'm doing wrong?

Support for a permanent clipped AppDrawer

I'm trying to make a permanent clipped navigation drawer with Material UI as per https://material.io/guidelines/patterns/navigation-drawer.html
Seems that there is a pull request out for this but not yet merged: https://github.com/callemall/material-ui/pull/6878
At this stage I'm trying to override with styles but can not get my left nav (paper) to apply the style marginTop: '50px',
Are there some samples out there on how to achieve this with v1.0.0-alpha.21?
They changed the way you have to override certain styles in v1. The inline styles no longer work. Certain parts of a component can be overridden with a simple className applied to the component. See this link for further details https://material-ui-1dab0.firebaseapp.com/customization/overrides.
Some deeper nested properties of certain components i.e the height of the Drawer can only be accessed by overriding the class itself. In this case the paper class of the drawer element.
This is a simple example
import React, { Component } from "react";
import Drawer from "material-ui/Drawer";
import { withStyles, createStyleSheet } from "material-ui/styles";
import PropTypes from 'prop-types';
const styleSheet = createStyleSheet("SideNav", {
paper: {
marginTop: '50px'
}
});
class SideNav extends Component {
....
render() {
return (
<Drawer
classes={{paper: this.props.classes.paper}}
docked={true}
>
....
</Drawer>
);
}
}
SideNav.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styleSheet)(SideNav);