Why getByRole does not accept the "ul" as a string? - reactjs

I'm testing a React app with React Testing Library.
I try to get a ul element like this:
const { render, getByRole } = require("#testing-library/react");
const { default: MoviesList } = require("../components/MoviesList");
const { default: MoviesProvider } = require("../utils/MoviesProvider");
describe("<MoviesList />", () => {
it("Component should exist", () => {
render(
<MoviesProvider value={{}}>
<MoviesList />
</MoviesProvider>
);
const ul = getByRole('ul');
expect(ul).toBeInTheDocument()
// or
expect(getByRole("ul"), isInTheDocument);
});
});
But I get this error:
TypeError: Expected container to be an Element, a Document or a DocumentFragment but got string.
Am I doing something wrong?

ul is an element, not an accessibility role.
The corresponding role is list :
const ul = getByRole('list');
expect(ul).toBeInTheDocument()
You can find a list of existing roles here https://www.w3.org/TR/wai-aria/#role_definitions

The problem was that I was importing getByRole on the top from #testing-library/react. When I tried to import it from render it worked.
Can anyone explain why?
const { getByRole } = render(
<MoviesProvider value={{}}>
<MoviesList />
</MoviesProvider>
);
const list = getByRole('list');

Related

Dynamically paste value in dot notation in react component

I'm trying to use the React-NBA-Logos package(npm link) in a small project and faced a question. According to the documentation here is an example of this package use
import React from 'react';
import * as NBAIcons from 'react-nba-logos';
const Example = () => {
return <NBAIcons.TOR />; // Loads the Toronto Raptors logo
};
export default Example;
The question is it possible to paste a variable with a dynamic string value(with some correct tripcode) here? Something like this
const Example = () => {
const NEW_VAR = 'NYK';
return <NBAIcons.NEW_VAR />;
};
You can get around this by creating an alias for the component.
const Example = () => {
const NEW_VAR = 'NYK';
const Component = NBAIcons[NEW_VAR];
return <Component />;
};
A functional approach would work well here:
function NBAIcon (name, props = {}) {
const Icon = NBAIcons[name];
return <Icon {...props} />;
}
function Example () {
return (
<ul>
<li>{NBAIcon('TOR')}</li>
<li>{NBAIcon('ATL', {size: '64px'})}</li>
</ul>
);
}

How can I pass a function when linking with React Router?

In my React app I have a page which has a list of web items (WebItems.js) and on the page there is an Link which goes to a page for adding a new web item (Add.js).
const WebItems = () => (
async function getWebItems() {
const result = await httpGet('webitem/list');
if (result.items) {
setWebItems(result.items);
}
}
return <>
<Link
to="webitem/list"
Add
</Link>
</>
)
I need to pass the function getWebItems() to the Add component so that after a web item is added the list of web items gets updated.
I am more familiar with #reach/router although I need to use react-router-dom for this project.
I found this blog post but I am unsure if this is what I need.
Can anyone help?
Why don't you just make this function a custom hook and re-use it?
// useWebItems.js
import { useState } from 'react';
function useWebItems() {
const [webItems, setWebItems] = useState([]);
async function getWebItems() {
const result = await httpGet('webitem/list');
if (result.items) {
setWebItems(result.items);
}
}
return { getWebItems, webItems };
}
export default useWebItems;
// Add Component
import { getWebItems, webItems } from 'path/to/useWebItems.js';
// Do whatever you want with getWebItems, webItems
You could use route state and the object form of the to prop for the Link.
const WebItems = () => (
async function getWebItems() {
const result = await httpGet('webitem/list');
if (result.items) {
setWebItems(result.items);
}
}
return <>
<Link
to={{
pathname: "webitem/list",
state: {
getWebItems,
},
}}
>
Add
</Link>
</>
);
Use location from the route props on the receiving route/component
const { getWebItems } = props.location.state;
...
getWebItems();

React Stripe Elements & SSR - Webpack Error: Window is Not Defined

React Stripe Elements works fine in development but deploying live via Netlify throws 'Webpack: Window is undefined' in Provider.js react stripe elements node module file.
As per some other suggestions I have tried ComponentDidMount method and also editing the Provider.js with this:
if (typeof window !== 'undefined') {
let iInnerHeight = window.innerHeight;
}
Both still result in failed deploys.
Also, I have tried setting stripe or apiKey in StripeProvider component, setting stripe throws error requiring Stripe object, e.g. Stripe(...) --> when switched with this get Stripe is not defined and apiKey throws window undefined error.
This is my gatsby-ssr.js file:
import React from 'react'
import { ShopkitProvider } from './src/shopkit'
import { StripeProvider, Elements } from 'react-stripe-elements'
import Layout from './src/components/Layout'
export const wrapRootElement = ({ element }) => {
return (
<StripeProvider apiKey={process.env.GATSBY_STRIPE_PUBLISHABLE_KEY}>
<ShopkitProvider clientId{process.env.GATSBY_MOLTIN_CLIENT_ID}>
<Elements>{element}</Elements>
</ShopkitProvider>
</StripeProvider>
)
}
export const wrapPageElement = ({ element, props }) => {
return <Layout {...props}>{element}</Layout>
}
Everything is working as expected on development, but SSR present window undefined issue with Webpack. I have also set env variables in Netlify as well in .env file
The problem is that there's a check for Stripe object in window inside StripeProvider. This means you can't use it raw in wrapRootElement. The simple solution is to not use StripeProvider in gatsby-ssr.js, you only need it in gatsby-browser.js.
However, since you're wrapping the root with multiple service providers, and also if you're loading Stripe asynchronously like this:
// somewhere else vvvvv
<script id="stripe-js" src="https://js.stripe.com/v3/" async />
You might as well make a common wrapper that can be used in both gatsby-ssr & gatsby-browser so it's easier to maintain.
I did this by creating a wrapper for StripeProvider where Stripe is manually initiated depending on the availability of window & window.Stripe. Then the stripe instance is passed as a prop to StripeProvider instead of an api key.
// pseudo
const StripeWrapper = ({ children }) => {
let stripe,
if (no window) stripe = null
if (window.Stripe) stripe = window.Stripe(...)
else {
stripeLoadingScript.onload = () => window.Stripe(...)
}
return (
<StripeProvider stripe={stripe}>
{children}
<StripeProvider>
)
}
This logic should be put in a componentDidMount or a useEffect hook. Here's an example with hook:
import React, { useState, useEffect } from 'react'
import { StripeProvider } from 'react-stripe-elements'
const StripeWrapper = ({ children }) => {
const [ stripe, setStripe ] = useState(null)
useEffect(() => {
// for SSR
if (typeof window == 'undefined') return
// for browser
if (window.Stripe) {
setStripe(window.Stripe(process.env.STRIPE_PUBLIC_KEY))
} else {
const stripeScript = document.querySelector('#stripe-js')
stripeScript.onload = () => {
setStripe(window.Stripe(process.env.STRIPE_PUBLIC_KEY))
}
}
}, []) // <-- passing in an empty array since I only want to run this hook once
return (
<StripeProvider stripe={stripe}>
{children}
</StripeProvider>
)
}
// export a `wrapWithStripe` function that can used
// in both gatsby-ssr.js and gatsby-browser.js
const wrapWithStripe = ({ element }) => (
<StripeWrapper>
<OtherServiceProvider>
{element}
</OtherServiceProvider>
</StripeWrapper>
)
By setting async to true in gatsby-config.js
{
resolve: `gatsby-plugin-stripe`,
options: {
async: true
}
}
It is possible to simplify the code above.
const Stripe = props => {
const [stripe, setStripe] = useState(null);
useEffect(() => {
(async () => {
const obj = await window.Stripe(process.env.STRIPE_PUBLIC_KEY);
setStripe(obj);
})();
}, []);
return (
<>
<StripeProvider stripe={stripe}>
{children}
</StripeProvider>
</>
);
};

React testing library - check the existence of empty div

I'm testing a component where if ItemLength = 1, render returns null.
const { container, debug } = render(<MyComp ItemLength={1} />);
When I call debug() in my test, it shows a <div />. How do I check that the component is returning an empty div in my test?
Update
Use toBeEmptyDOMElement since toBeEmtpy has been deprecated.
You can use jest-dom's toBeEmpty:
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeEmpty()
The following should work as well without extending jest's expect:
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeNull();
Update: the new way in 2020
import { screen } from '#testing-library/react';
...
render(<MyComp ItemLength={1} />);
const child = screen.queryByTestId('data-testid-attribute-value');
expect(child).not.toBeInTheDocument();
.toHaveLength(0) should also work without jest-dom extension
const wrapper = render(<MyComp ItemLength={1}/>);
expect(wrapper.container.innerHTML).toHaveLength(0);
toBeEmpty - throw warning, you must use toBeEmptyDOMElement instead
const pageObject = cretePage(<UserResources />, appState);
expect(pageObject.noContent).toBeInTheDocument();
expect(pageObject.results).toBeEmptyDOMElement();
Since you are trying to test for empty div, one way you could try to test it is by matching node (another possible solution is number of nodes rendered)
getByText(container, (content, element) => element.tagName.toLowerCase() === 'div')
You can use js-dom's toBeEmptyDOMElement method. https://github.com/testing-library/jest-dom#tobeemptydomelement
Before you can use toBeEmptyDOMElement you will need to install jest-dom and set up jest. https://github.com/testing-library/jest-dom#usage
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeEmptyDOMElement()
Note: toBeEmpty method is being deprecated and its suggested to use toBeEmptyDOMElement
To extend off the previous answers; the following works and is probably a bit cleaner with no dependence on the data-testid or screen to check for roles.
import { render } from "#testing-library/react";
// Components
import { YourComponent } from "<Path>/YourComponent";
describe("YourComponent", () => {
it("component should be an empty element", () => {
const { container } = render(<YourComponent />);
expect(container).toBeEmptyDOMElement();
});
it("component should not be an empty element", () => {
const { container } = render(<YourComponent />);
expect(container).not.toBeEmptyDOMElement();
});
});

How to assert a React component with multiple <a> tags has an <a> tag with a given href

I'm trying to write a mocha test for a React component. Basically the component needs to render an <a> tag with its href set to a value in a property that is passed in. The issue is that the component can render multiple <a> tags in an unpredictable order and only one of them has to have the correct href.
I'm using enzyme, chai and chai-enzyme
The following is a cut down version of my real code, but neither of the tests are passing:
const TestComponent = function TestComponent(props) {
const { myHref } = props;
return (
<div>
Link 1<br />
<a href={myHref}>Link 2</a><br />
Link 3
</div>
);
};
TestComponent.propTypes = {
myHref: React.PropTypes.string.isRequired
};
describe('<TestComonent />', () => {
it('renders link with correct href', () => {
const myHref = 'http://example.com/test.htm';
const wrapper = Enzyme.render(
<TestComponent myHref={myHref} />
);
expect(wrapper).to.have.attr('href', myHref);
});
it('renders link with correct href 2', () => {
const myHref = 'http://example.com/test.htm';
const wrapper = Enzyme.render(
<TestComponent myHref={myHref} />
);
expect(wrapper.find('a')).to.have.attr('href', myHref);
});
});
It turns out that I was approaching this wrong. Rather than try to get the assertion part of the expression to work with multiple results from the query, it is better to change the find query to be more specific. It is possible to use attribute filters in a similar way to jQuery. As such my test becomes like this:
const TestComponent = function TestComponent(props) {
const { myHref } = props;
return (
<div>
Link 1<br />
<a href={myHref}>Link 2</a><br />
Link 3
</div>
);
};
TestComponent.propTypes = {
myHref: React.PropTypes.string.isRequired
};
describe('<TestComonent />', () => {
it('renders link with correct href', () => {
const myHref = 'http://example.com/test.htm';
const wrapper = Enzyme.render(
<TestComponent myHref={myHref} />
);
expect(wrapper.find(`a[href="${myHref}"]`)).be.present();
});
});

Resources