verify react functional component to be there - javascript

I'm new to react unit testing, here I have react code which works fine but need to unit test it.
I want to verify the component to be there, I have tried in two different ways and both of them does not work:
I'm using useSelector and useDispatch thats why connect(null,null).
my code:
M2SelectionCard.js:
function ConnectedSelectionCard({ classes }) {
return (
<Card data-testid="M2SelectionCardd" className={classes.selectionCard}>
<CardContent>
</CardContent>
</Card>
);
}
const M2SelectionCard = connect(null, null)(ConnectedSelectionCard);
export default M2SelectionCard;
first I did like this:
import React from "react";
import { expect } from "#jest/globals";
import { render, screen, cleanup } from "#testing-library/react";
import M2SelectionCard from "../M2SelectionCard";
test("test", () => {
render(<M2SelectionCard />);
const SelectionCardElement = screen.getByTestId("M2SelectionCardd");
expect(SelectionCardElement).toBeInTheDocument();
// expect(true).toBe(true);
});
and got error : Could not find "store" in the context of "Connect(ConnectedSelectionCard)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(ConnectedSelectionCard) in connect options.'
import React from "react";
import { expect } from "#jest/globals";
import { render, screen, cleanup } from "#testing-library/react";
import M2SelectionCard from "../M2SelectionCard";
import { Provider } from "react-redux";
import configureStore from "../../redux/store";
const store = configureStore({});
it("test", () => {
render(
<Provider store={store}>
<M2SelectionCard />
</Provider>
);
const SelectionCardElement = screen.getByTestId("M2SelectionCardd");
expect(SelectionCardElement).toBeInTheDocument();
});
after that I added store to it in test (don't know should I add it here ?):
error message:
TypeError: Cannot read property 'selectionCard' of undefined
it points to className={classes.selectionCard}
any idea ?

TypeError: Cannot read property 'selectionCard' of undefined'
pointing to className={classes.selectionCard}
This error is saying that it cannot access a selectionCard property of an undefined object, classes in this case.
Given component under test:
function ConnectedSelectionCard({ classes }) {
...
return (
<Card data-testid="M2SelectionCardd" className={classes.selectionCard}>
<CardContent>
</CardContent>
</Card>
);
}
You should still pass all the expected props, i.e. a classes prop. For this purpose an empty object is sufficient enough to access into. In other words, if classes is an empty object then classes.selectionCard evaluates to undefined instead of throwing an error.
it("test", () => {
render(
<Provider store={store}>
<M2SelectionCard classes={{}} /> // <-- pass a classes prop
</Provider>
);
const SelectionCardElement = screen.getByTestId("M2SelectionCardd");
expect(SelectionCardElement).toBeInTheDocument();
});

Related

Error when put <BrowserRouter> and useRoutes(routes) in A function component in React

I'm using react-router-dom in my react app.
When I use:
function App() {
return (
<BrowserRouter>{ useRoutes(routes) }</BrowserRouter>
);
}
And I got error message: Uncaught Error: useRoutes() may be used only in the context of a <Router> component.
I've searched how to fix it and change my code's structure to:
function App() {
return (
<BrowserRouter><Foo/></BrowserRouter>
);
}
function Foo() {
return useRoutes(routes)
}
The code works properly.
As a starter, I can't tell the exact difference between the two snippet above, could someone please help?
Uncaught Error: useRoutes() may be used only in the context of a <Router> component.
All this message is saying is that there needs to be a routing context provider higher in the ReactTree than this App component that is trying to consume it when using the useRoutes hook.
A clearer bad example:
function App() {
const routes = useRoutes(routes); // <-- (2) but needed here!
return (
<BrowserRouter> // <-- (1) context provided here
{routes}
</BrowserRouter>
);
}
Here you can clearly see that the router is below the App component and there is no routing context provided to App.
This is why the second snippet works, it's providing the routing context higher than the component consuming it.
function App() {
return (
<BrowserRouter> // <-- (1) context provided here
<Foo/> // <-- (2) context consumed here
</BrowserRouter>
);
}
function Foo() {
return useRoutes(routes);
}
Here's another example where the error was thrown and was fixed thanks to #drew-reese's explanation:
create-react-app builds the following TypeScript test file:
import React from 'react';
import { render, screen } from '#testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
The fix is to include BrowserRouter
import { BrowserRouter } from "react-router-dom";
and to wrap <App /> to get the routing context:
render(<BrowserRouter><App /></BrowserRouter>);

Cannot access {variable-name} before initialization (React import)

I'm getting a Reference Error
Cannot access 'STRUCTURE_COLUMN_ID' before initialization
I have a structureColumn file which contains the following:
import React from 'react';
import Column from './column';
export const STRUCTURE_COLUMN_ID = 'Structure';
export default class StructureColumn extends Column {
constructor(name) {
super(STRUCTURE_COLUMN_ID);
}
clone() {
return new StructureColumn(this.name);
}
getKey() {
return STRUCTURE_COLUMN_ID;
}
}
When trying to access StructureColumn class or STRUCTURE_COLUMN_ID variable from a component I get the mentioned error.
The component looks the following:
import React from 'react';
import { List, ListItem, Tooltip } from '#material-ui/core';
import { STRUCTURE_COLUMN_ID } from '../../../../models/structureColumn';
console.log(STRUCTURE_COLUMN_ID);
const CustomColumnsList = ({ onSelect }) => {
return (
<List>
{Object.values({}).map(col => (
<Tooltip title={col.description}>
<ListItem onClick={() => onSelect(col.column)} button>
{col.name}
</ListItem>
</Tooltip>
))}
</List>
);
};
export default CustomColumnsList;
I can use the variable inside the functional component body but the thing is I wanted to create a constant variable out of it's scope. Never seen this issue before in React. Someone has an experience dealing with it?

React-testing-library: Could not find "store" in the context of component

I'm quite new to testing so try to be gentle! I am trying to set up react-testing-library and I'm stuck with a confusing error message:
I defined a custom renderer that contains all my providers incl. redux Provider:
import React from 'react';
import { render } from '#testing-library/react'
import { ThemeProvider } from 'styled-components';
import { defaultTheme } from 'shared/theme';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from 'reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
const AllTheProviders = ({ children }) => {
return (
<Provider store={store}>
<ThemeProvider theme={defaultTheme}>
{children}
</ThemeProvider>
</Provider>
)
}
const customRender = (ui, options?) =>
render(ui, { wrapper: AllTheProviders, ...options })
// re-export everything
export * from '#testing-library/react'
// override render method
export { customRender as render }
But when trying to execute the test I am receiving the error Message:
Error: Uncaught [Invariant Violation: Could not find "store" in the
context of "Connect(withRouter(Header))". Either wrap the root
component in a , or pass a custom React context provider to
and the corresponding React context consumer to
Connect(withRouter(Header)) in connect options.]
As far as I can see, I included the store. Is there a way around this?
This question is not a duplicate of: Invariant Violation: Could not find "store" in either the context or props of "Connect(SportsDatabase)"
Since it involves a global renderer.
The store needs to be mocked in order to test properly. I suggest to use redux-mock-store library to achieve that but it's up to your preference.
What I would try in your case is the following in the test file:
import configureMockStore from 'redux-mock-store';
const mockStore = configureMockStore();
const store = mockStore({ /* here you can create a mock object for the reducer */ });
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Provider store={store}>
{ /* your components */ }
</Provider>, div);
ReactDOM.unmountComponentAtNode(div);
});
I hope this gives you the idea and helps!

React context returns undefined

I want to use context in React to pass authentication information to child components. My context provider is in a file named AuthContext.js:
import React, { Component, createContext } from 'react';
export const AuthContext = createContext();
class AuthContextProvider extends Component {
state = {
isAuthenticated: false
};
toggleAuth = () => {
this.setState({ isAuthenticated: !this.state.isAuthenticated });
};
render() {
return (
<AuthContext.Provider value={{...this.state, toggleAuth: this.toggleAuth}}>
{this.props.children}
</AuthContext.Provider>
);
}
}
export default AuthContextProvider;
But when I'm accesing the context from the child components, it returns undefined
import { AuthContext } from "../contexts/AuthContext";
export default function ButtonAppBar() {
return(
<AuthContext.Consumer>{(authContext) => {
console.log(authContext);
}}</AuthContext.Consumer>
);
}
After checking the React Developer Tools, I came to see that ButtonAppBar is not inside AuthContextProvider. Imports are correct. Any reason for this apart from import errors?
React version: 16.9.0
If you take a look at this working codesandbox you will see that your code is correct and should be working if you did everything all right.
Reasons that can make it not work
Maybe you are importing it wrong? Please check how you import it.
Maybe ButtonAppBar isn't inside AuthContextProvider.
For the context to work, the consumer needs to be inside of the provider.
This will work
<AuthContextProvider>
<ButtonAppBar /> {// consumer inside the provider }
</AuthContextProvider>
This will not work
<AuthContextProvider>
...
</AuthContextProvider>
<ButtonAppBar /> {// consumer outside the provider }
In the screenshot from the question, you don't have AuthContextProvider wrapping the routes, so you will never be able to get the context.
You didn't provided the code correctly but I'm guessing that what you need to do is
<App>
<AuthContextProvider> {// AuthContextProvider wrapping routes so you can get the context}
<BrowserRouter>
...
</BrowserRouter>
</AuthContextProvider>
</App>

React Router & Global Context

I'm building an e-commerce website with React (my first ever React project) and I'm using React router to manage my pages.
I've got the following component tree structure:
<Router>
<BrowserRouter>
<Router>
<withRouter(Base)>
<Route>
<Base>
<BaseProvider>
<Context.Provider>
<Header>
<PageContent>
The standard React Router structure basically, and withRouter I've got the following:
Base.js
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { Header } from './Header';
import { Footer } from './Footer';
import Provider from '../../BaseProvider';
class Base extends Component {
render() {
return (
<Provider>
<Header/>
<div className="container">{this.props.children}</div>
<Footer />
</Provider>
);
}
}
BaseProvider.js
import React, { Component, createContext } from 'react';
const Context = createContext();
const { Provider, Consumer } = Context;
class BaseProvider extends Component {
state = {
cart: [],
basketTotal: 0,
priceTotal: 0,
};
addProductToCart = product => {
const cart = { ...this.state.cart };
cart[product.id] = product;
this.setState({ cart, basketTotal: Object.keys(cart).length });
};
render() {
return (
<Provider
value={{ state: this.state, addProductToCart: this.addProductToCart }}
>
{this.props.children}
</Provider>
);
}
}
export { Consumer };
export default BaseProvider;
This gives me a template essentially, so I just the children pages without having to include Header and Footer each time.
If I want to use my global context I'm having to import it each time, and it seems like I've done something wrong as surely I should be able to use this on any page since it's exported in BaseProvider?
If I was to visit the About page, I'd get the same component structure, but no access to the consumer without using:
import { Consumer } from '../../BaseProvider';
Why do I have to do this for each file even though it's exported and at the top level of my BaseProvider? It just seems such a bad pattern that I'd have to import it into about 20 files...
Without importing it, I just get:
Line 67: 'Consumer' is not defined no-undef
I tried just adding the contextType to base but I get: Warning: withRouter(Base): Function components do not support contextType.
Base.contextType = Consumer;
I feel like I've just implemented this wrong as surely this pattern should work a lot better.
I'd recommend using a Higher Order Component - a component that wraps other components with additional state or functionality.
const CartConsumer = Component => {
return class extends React.Component {
render() {
return (
<MyContext.Consumer>
<Component />
</MyContext.Consumer>
)
}
}
}
Then in any component where you'd like to use it, simply wrap in the export statement:
export default CartConsumer(ComponentWithContext)
This does not avoid importing completely, but it's far more minimal than using the consumer directly.

Categories