Query a simple button with jest and - reactjs

I'm trying to test my component, so here it's :
import React from 'react';
import { useNavigate } from 'react-router-dom';
import '../styles/mainPageStyles.css';
const MainPage = (): React.ReactElement => {
const navigate = useNavigate();
const handleBeginQuiz = () => {
navigate('/quiz');
};
return (
<div className="mainPageContainer">
<div className="mainpageWrapper">
<h2 className="defaultFontSize">Welcome to the Trivia Challenge!</h2>
<p className="defaultFontSize">
You will be presented with 10 True or False questions.
</p>
<p className="defaultFontSize">Can you score 100%?</p>
<button
className="beginButton defaultFontSize"
onClick={handleBeginQuiz}
aria-label="BEGIN"
>
BEGIN
</button>
</div>
</div>
);
};
export default MainPage;
as you see it has only one functionality, to redirect me to another page,
I ended to test on click event on the button it self,
It seems like I can't select it, I always get this error:
Main Page Tests › On Begin Button Click
TestingLibraryElementError: Unable to find an element with the text: BEGIN. This could be because the
text is broken up by multiple elements. In this case, you can provide a function for your text matcher to
make your matcher more flexible.
and here are my attempts:
test('On Begin Button Click', () => {
const history = createMemoryHistory();
render(
<MemoryRouter initialEntries={[`/`]}>
<Routes>
<Route element={<MainPage />} />
</Routes>
</MemoryRouter>
);
// I have also tried getByText
const buttonElement = screen.getAllByText('BEGIN', { selector: 'button' });
// fireEvent.click(buttonElement);
// expect(history.location.pathname).toBe('/quiz');
});

try using findByText instead of getByText

Related

rendering a component inside a react page using Menu

I have a menu and the main body of the page. I have created different pages as components with some text. All I want is that when I click on the side bar menu, the components are displayed in the main page. How can I make this work?
const items2 = [{
label: 'Rice',
key: 'rice',
},
{
label: 'AB Test',
key: 'ab',
}]
const MainLayout = () => {
const {
token: { colorBgContainer },
} = theme.useToken();
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
return (
<Layout>
<Sider >
<Menu
mode="inline"
items={items2}
onClick = {onClick}
/>
</Sider>
<Content >
//I Want to open the pages here
</Content>
</Layout>
</Content>
To render a component inside other component, React provides a special props name children.
To achieve your requirement, you can do like this:
MainLayout.js:
export const MainLayout = ({children}) => {
const {
token: { colorBgContainer },
} = theme.useToken();
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
return (
<Layout>
<Sider>
<Menu
mode="inline"
items={items2}
onClick={onClick}
/>
</Sider>
<Content>
{children}
</Content>
</Layout>
)
}
In MainLayout.js, you only need to write {children} inside component <Content>, React will do the rest for you to pass the content of Rice or AB or whatever for you. In each page, just place <MainLayout> at the top of the outer of rest of your code.
Please see 2 example files below.
Rice.js:
import MainLayout from './MainLayout';
export const Rice = () => {
// Do some stuffs here...
return (
<MainLayout>
<div>
<h2>Best rated rice in the World</h2>
<ol>
<li>Pinipig</li>
<li>Riz de Camargue</li>
...
</ol>
<div>
</MainLayout>
)
}
Corn.js:
import MainLayout from './MainLayout';
export const Corn = () => {
// Do some stuffs here...
return (
<MainLayout>
<div>
<h2>Best Corn Side Dish Recipes</h2>
<ol>
<li>Whipped-Cream Corn Salad</li>
<li>Classic Grilled Corn on the Cob</li>
...
</ol>
<div>
</MainLayout>
)
}
You can read more and play around with the example code from React's official documents.
It is the basic concept of React, so before you start to build something big, I suggest to follow this docs first or find some series of React tutorials for beginner, they will explain key concepts of React so you would not save more time.
You need to use react-router-dom to navigate when you click other MenuItem. Create your own RouterProvider and put it in the Content.
<Content>
<RouterProvider router={router}>
</Content>
EDIT
Now you have two way to navigate to your Component. First is using Link and set it to your label.
{
label: <Link title="Rice" to="/rice">Rice</Link>,
key: 'rice',
}
Second way is hook useNavigate
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
//Add to Menu
<Menu
onClick={onClick}
//other props
/>

Using useHistory on React

I am a beginner in React and trying to learn things by myself. I have this code that I'd like to navigate to the Login page using useHistory but I can't seem to make it work. Hope you can help me. Here is my code below:
import { useHistory } from "react-router-dom";
const App = () => {
let history = useHistory();
const MoveToLogin = () => {
history.push('./container/Login');
}
return (
<div>
<button className='btn' text='User Login' onClick=.
{MoveToLogin}>Login</button>
</div>
);
}
export default App;
First you need to add Provider 'Router' which is imported from 'react-router-dom'
Define routes and corresponding components in routing file.
You can only use history inside the children of Provider Router.
Navigate to route using history.push('/login'). Don't provide relative path of component file here. Use the route you want to show in browser url
I have played around and did more research about useHistory. I was able to make it work. It has navigated to a new component. Please find my solution below. Hope it can help others with the same kind of issue.
import UserLogin from "./pages/UserLogin";
const ButtonLogin = () => {
let history = useHistory();
const MoveToLogin = () => {
history.push('/pages/UserLogin');
}
return (
<div><button className='btn btn-primary' onClick={MoveToLogin}>Login</button></div>
);
}
const App = () => {
return (
<div>
<Router>
<Route path="/pages/UserLogin" exact component={UserLogin} />
<Route path="/" exact component={ButtonLogin} />
</Router>
</div>
);
}
export default App;
Refer to this answer as well for further information.
Cannot read property 'push' of undefined for react use history
I hope you have the routes defined for the path specified
<Router>
<Route path="/container/Login" exact component={LoginComponent} />
</Router>
The dot is not needed in your move to login function.
import { useHistory } from "react-router-dom";
const App = () => {
let history = useHistory();
const MoveToLogin = () => {
history.push('/container/Login'); // Here you don't need dot ./container
}
return (
<div>
<button className='btn' text='User Login' onClick=.
{MoveToLogin}>Login</button>
</div>
);
}
export default App;

How would I test this using Jest & React Testing library?

I have a component that I would like to test using Jest and React Testing Library. When I say test, I'm basically saying that I want to check if the content shows up on the screen. However, I'm running into a serious problem because I'm dealing with an async operation that updates the state, so the content is not appearing immediately. How would I approach this problem? A code snippet would be much appreciated.
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
const Home = () => {
const [tv, setTv] = useState([]);
const [tvLoading, setTvLoading] = useState(true);
// Go and fetch popular TV shows
const getPopularTv = async () => {
axios.get( ... )
setTv(data);
setTvLoading(false);
};
// This will run once. As soon as the component gets rendered for the 1st time
useEffect(() => {
getPopularTv();
}, []);
let TvData, loading;
const img_path = 'https://image.tmdb.org/t/p/w500/';
// If we have TV shows, set the 'TvData' variable to a pre-defined block of JSX using it.
if (tv && tv.total_results > 0) {
TvData = (
<div className="row animated fadeIn ">
{tv.results.slice(0, 10).map((show) => {
return (
// I WANT TO TEST IF THIS DIV APPEARS ON THE SCREEN
// SO, ON THIS DIV I'M SETTING UP THE 'data-testid'
// HOWEVER THIS IS A ASYNC OPERATION AND THE CONTENT
// WON'T SHOW UP IMMEDIATELY. HOW WOULD I TEST THIS???
<div
data-testid="home-shows" // HERE'S THE ID THAT I WANT TO USE IN MY TEST
className="col s6 m6 l6"
key={show.id}
>
<Link to={'/tvs/' + show.id}>
<img
className="responsive-img z-depth-3 poster tooltipped"
data-tooltip={show.name}
data-position="top"
src={img_path + show.poster_path}
alt={show.name}
/>
</Link>
</div>
);
})}
</div>
);
}
// Set up the 'loading' screen
loading = (
<div className="progress">
<div className="indeterminate"></div>
</div>
);
return (
<div className="container">
{tvLoading ? loading : TvData}
</div>
);
};
export default Home;
I've tried a combination of act, findByTestId, waitFor, etc. But I can't get it to work properly.
For example, I tried something like this:
it('should display TV shows', async () => {
const { getByText, findByTestId } =
render(
<BrowserRouter>
<Home />
</BrowserRouter>
)
await findByTestId('home-shows')
expect(getByText('More Info')).toBeInTheDocument();
});
My thinking was, if the content appears then it should contain the text of "More Info". If that's not the case the content is not visible, so the test should fail. however, the test fails regards if the content appears or not and I'm getting an error that I should wrap my test inside of an act() callback.
Thanks to #EstusFlask I came to a breakthrough. The solution was to use waitFor.
This is how I solved the problem:
it('should display movies', async () => {
render(
<BrowserRouter>
<Home />
</BrowserRouter>
);
const data = await waitFor(() => screen.findByTestId('home-shows'));
expect(data).toBeTruthy();
});

How to dynamically create a React component based on url param?

I'd like my React component to generate based on the url param, in this case, the :id param. I'm struggling rendering the component. I have the different Components defined in separate files (ex. Loader.js, Radio button.js, Accordion menu.js).
Here's my (reduced for clarity) code that is continuously failing :)
import React from 'react';
import { Switch, Link, Route } from 'react-router-dom';
import Grid from '../Components/Grid'
function Overview () {
const components = [
{id: 'accordion-menu',
name: 'Accordion menu'},
{id: 'radio-button',
name: 'Radio button'},
{id: 'loader',
name: 'Loader'},
]
const componentPage = ({match}) => {
const findId = components.find((el) => {
match.params.id = el.id;
return findId.name;
}
)}
return (
<Router>
<div className="components">
<h3>Components</h3>
<p>This header and the menu will always appear on this page!</p>
<menu>
{components.map(({id, name}) => (
<li>
<Link to={`/components/${id}`}>{name}</Link>
</li>
))}
</menu>
<Switch>
<Route exact path={'/components/'} component={Grid}/>
<Route path={'/components/:id'} component={componentPage}/>
</Switch>
</div>
</Router>
)
}
export default Overview;
const componentPage = ({match}) => {
const findId = components.find((el) => {
match.params.id = el.id;
return findId.name;
}
)}
I think what you mean here is to return el.name not findId.name.
the other thing is you are trying to display the function as a component which won't work.

React - Show only the clicked user

In the following app, I'm accessing the random user API and show a list of 12 users.
App.js
import React, { useState } from 'react'
import UserList from './components/UserList'
const App = props => {
const [id, setID] = useState(null)
console.log(`Passed variable to App.js is: ` + id)
return (
<>
<UserList setID={setID} />
</>
)
}
export default App
UserList.js
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const UserList = ({ setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me/?results=12'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
return (
<ul>
{resources.map(item => (
<li key={item.name.first}>
<div>
<h2>{item.name.first} {item.name.last}</h2>
<button
onClick={() => setID(item.login.uuid)}
>
Details
</button>
</div>
</li>
))}
</ul>
)
}
export default UserList
The above code is working. But now I want that if I click on the button for any of those listed users, only that user get showed.
How can I do that?
The response JSON looks like this:
Easiest way would be to apply a filter on your ressources variable to only display the user with selected uuid.
To do that, first you need to share selected id with UserList component:
App.js
<UserList id={id} setID={setID} />
Then update UserList accordingly:
UserList.js
const UserList = ({ id, setID }) => {
return (
<ul>
{ resources
.filter(user => Boolean(id) ? user.login.uuid == id : true )
.map(item => (
<li key={item.name.first}>
<div>
<h2>{item.name.first} {item.name.last}</h2>
{ Boolean(id) ?
<button onClick={() => setID(null)}>
Hide
</button>
:
<button onClick={() => setID(item.login.uuid)}>
Details
</button>
}
</div>
</li>
)
}
</ul>
)
}
That way, you will only display the select user in you <ul>. To unselect your user, just call setID(null)
Show user profile instead of list
If that solution work to filter your list, I guess you might want to adapt your page to show all details from your user. Next step would be to implement multi pages using react-router-dom with a url container your user uuid.
You can look at the url-params example which might be exactly what you are looking for.
Here's a slightly detailed option that extends beyond a single component but more easy to scale on account of modularity.
Create a new react component in a new file say, UserDetails.js
Now you need a way to navigate to this new page when the button is clicked.
So in your App.js you need a router like
import { BrowserRouter, Switch} from 'react-router-dom'
Then in your App.js file wrap all your components in the router:
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path="/user-list" component={UserList} />
<Route exact path="/detail" component={UserDetails}/>
</Switch>
</div>
</BrowserRouter>
);
}
}
export default App;
Now you are ready to navigate to the user details page, when the button is clicked. So add a function like goToDetails like:
<button onClick={() => goToDetails(item)}>
Next define the function that navigates to the next page
goToDetails(item) {
this.props.history.push('/detail', {selectedUser:item:});
}
The history prop is available above because we earlier wrapped the entire app in BrowserRouter.
In the details page, you get the selectedUser details as a prop:
const selectedUser = this.props.location.state.selectedUser;
Now you can render it however you want.

Resources