I'm writing test code for my react app. I want to write test code that tests screen transition from Page A to Page B.
However, somehow the event to make user move to Page B from Page A doesn't work in my test code.
This is the test code.
Test code
import { fireEvent, render, screen } from '#testing-library/react';
import React from 'react';
import { StaticRouter } from 'react-router-dom';
import Routes from '../src/rouer';
const App = () => {
return (
<StaticRouter>
<Routes />
</StaticRouter>
);
};
describe('Test screen transition', () => {
it('Test screen transition', () => {
render(<App />);
screen.debug();
// I want this line makes the click event fired, but it doesn't
fireEvent.click(screen.getByText('Go to Page B from Page A'));
screen.debug();
});
});
This is my Page A component
import * as React from 'react';
import { Link } from 'react-router-dom';
const A = () => {
return (
<>
<div>
<Link to="/b">Go to Page B from Page A</Link>
</div>
</>
);
};
export default A;
This is my Page B component
import * as React from 'react';
import { Link } from 'react-router-dom';
const B = () => {
return (
<>
<div>
This is Page B
</div>
</>
);
};
export default B;
Related
I tried to debug my code but I lost right now.
Basicly I created State that open Login window and overlay that are Portals, when someone click the login button. its works fine but my problem is that I want when someone click on the overlay that the login window and the overlay will close.
I can't achieve that unfortunately.
Thanks For Advance !
Here is the code :
App.js
import { useState } from "react";
import Home from "./components/Elements/Home";
import Footer from "./components/UI/Footer";
import NaviBar from "./components/UI/NaviBar";
import "./App.css";
import Auth from "./components/Auth/Auth";
function App(props) {
const [openWindow, setOpenWindow] = useState(false);
const clickImport = (data) => {
setOpenWindow(true);
};
const closeHandler = () => {
setOpenWindow(false);
};
return (
<>
<NaviBar dataClick={clickImport} />
{openWindow && <Auth dataClickClose={closeHandler} />}
<Home />
<Footer />
</>
);
}
export default App;
Auth.js
import { useState } from "react";
import ReactDOM from "react-dom";
import classes from "./Auth.module.css";
import Login from "./Login";
import Overlay from "./Overlay";
const Auth = (props) => {
return (
<>
{ReactDOM.createPortal(<Login />, document.getElementById("auth"))}
{ReactDOM.createPortal(
<Overlay onClick={props.dataClickClose} />,
document.getElementById("blur")
)}
</>
);
};
export default Auth;
Basicly I created State that open Login window and overlay that are Portals, when someone click the login button. its works fine but my problem is that I want when someone click on the overlay that the login window and the overlay will close.
Solved It.
I forgot to update the Overlay component.
here is my fix:
import classes from "./Overlay.module.css";
const Overlay = (props) => {
return <div className={classes.bgContainer} onClick={props.closeClick}></div>;
};
export default Overlay;
So ive got a list of restaurant names (say fetched from an api). When I click on a restaurant name, I want it to link to a profile page for that specific restaurant, and this would set the text as "selected". And when I click "Go back" on that profile page to return to the home page, I want the that restaurant name to say "not selected".
So, if I click on the restaurant name, then in the profile page go back to the home page, the restaurant will show "unselected" since it was selected in the home page, then unselected in the profile page. However, if I click on the restaurant name, then instead of going back to the home page by clicking the "go back", I type in the url of the home page, it will show "selected".
I'm struggling with making it so when I click "Go back", the home page shows the restaurant name as having "unselected".
https://codesandbox.io/s/serene-williams-2snv1c?file=/src/App.js
(I would also appreciate if I could get the name of this sort of concept so I can look it up myself)
If I'm understanding the question correctly, you want to set some "selected" state, and only clear it if the link from the detail page is clicked.
You can create a React Context to hold and provide out the clickedRestaurants state and updater functions.
The idea here is to use the selectRestaurant handler when navigating "forward" to the details page, and use the deselectRestaurant handler only when the link from the details page back to the home page is clicked. If a user navigates to the home page using any other method, the restaurant won't be de-selected.
The localStorage API is used to persist state changes and initialize the state. The resolves persisting the selected restaurants state when the page is reloaded or a user directly mutates the URL in the address bar, i.e. like manually navigating back to "/".
RestaurantProvider
import { createContext, useContext, useEffect, useState } from "react";
export const RestaurantContext = createContext();
export const useRestaurantContext = () => useContext(RestaurantContext);
const RestaurantProvider = ({ children }) => {
const [clickedRestaurants, setClickedRestaurants] = useState(() => {
return JSON.parse(localStorage.getItem("clickedRestaurants")) ?? {};
});
useEffect(() => {
localStorage.setItem(
"clickedRestaurants",
JSON.stringify(clickedRestaurants)
);
}, [clickedRestaurants]);
const setRestaurantState = (id, selected) => {
setClickedRestaurants((ids) => ({
...ids,
[id]: selected
}));
};
const selectRestaurant = (id) => setRestaurantState(id, true);
const deselectRestaurant = (id) => setRestaurantState(id, false);
return (
<RestaurantContext.Provider
value={{ clickedRestaurants, selectRestaurant, deselectRestaurant }}
>
{children}
</RestaurantContext.Provider>
);
};
export default RestaurantProvider;
index.js - Import and wrap the application components with the RestaurantProvider component created above.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import RestaurantProvider from "./RestaurantProvider";
import App from "./App";
import Details from "./details";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<RestaurantProvider>
<App />
<Details />
</RestaurantProvider>
</StrictMode>
);
App - Import and use the useRestaurantContext hook to access the state and updater functions.
import "./styles.css";
import { Link, Route } from "wouter";
import data from "./data";
import { useRestaurantContext } from "./RestaurantProvider";
export default function App() {
const { clickedRestaurants, selectRestaurant } = useRestaurantContext();
return (
<Route path="/">
<div className="App">
{data.map((restaurant) => {
return (
<Button
key={restaurant}
restaurant={restaurant}
hasBeenClicked={clickedRestaurants[restaurant]}
setClicked={() => selectRestaurant(restaurant)}
/>
);
})}
</div>
</Route>
);
}
function Button({ restaurant, hasBeenClicked, setClicked }) {
return (
<>
<Link href={`/restaurant/${restaurant}`} onClick={setClicked}>
<button>{restaurant}</button>
</Link>
<p>
{restaurant} has {hasBeenClicked ? "" : "not "}been selected
</p>
</>
);
}
Details
import "./styles.css";
import { Link, Route } from "wouter";
import { useRestaurantContext } from "./RestaurantProvider";
export default function Details() {
const { deselectRestaurant } = useRestaurantContext();
return (
<div className="App">
<Route path="/restaurant/:name">
{(params) => {
const restaurant = decodeURI(params.name);
return (
<Link href="/" onClick={() => deselectRestaurant(restaurant)}>
{restaurant} Go back and unselect
</Link>
);
}}
</Route>
</div>
);
}
You'll need to define some sort of state if you want to be able to tell what has been clicked by the user and what hasn't. Here is one way to do it:
App.js
import "./styles.css";
import data from "./data";
import { Link, Route } from "wouter";
import { useState } from "react";
export default function App() {
const [clickedRestaurants, setClickedRestaurants] = useState([])
return (
<Route path="/">
<div className="App">
{data.map((restaurant) => {
return (
<Button
restaurant={restaurant}
hasBeenClicked={clickedRestaurants.includes(restaurant)}
setClicked={() => {
if (!clickedRestaurants.includes(restaurant)) {
setClickedRestaurants([...clickedRestaurants, restaurant])
}
}}
/>
);
})}
</div>
</Route>
);
}
function Button({ restaurant, hasBeenClicked, setClicked }) {
return (
<>
<Link href={`/restaurant/${restaurant}`} onClick={setClicked}>
<button>{restaurant}</button>
</Link>
<p>{restaurant} has {hasBeenClicked ? "" : "not "}been clicked</p>
</>
);
}
I am using contextApi with nextjs and I'm having some trouble when providing a context just for certain routes. I am able to make the context available for just a few routes, but when I transition from one to the other through linking, I end up losing the state of my application.
I have three files inside my pages folder:
index.tsx,
Dashboard/index.tsx and
SignIn/index.tsx.
If I import the provider inside the files Dashboard/index.tsx and SignIn/index.tsx and go from one page to the other by pressing a Link component from next/link, the whole state is set back to the initial state.
The content of the Dashboard/index.tsx file
import React from 'react';
import Dashboard from '../../app/views/Dashboard';
import { AuthProvider } from '../../contexts/auth';
const Index: React.FC = () => (
<AuthProvider>
<Dashboard />
</AuthProvider>
);
export default Index;
This is the contend of the SignIn/index.tsx file:
import React from 'react';
import SignIn from '../../app/views/SignIn';
import { AuthProvider } from '../../contexts/auth';
const Index: React.FC = () => (
<AuthProvider>
<SignIn />
</AuthProvider>
);
export default Index;
The views folder is where I create the components that will be rendered.
The content of the file views/SignIn/index.tsx is:
import React, { useContext } from 'react';
import Link from 'next/link';
import { AuthContext } from '../../../contexts/auth';
const SignIn: React.FC = () => {
const { signed, signIn } = useContext(AuthContext);
async function handleSignIn() {
signIn();
}
return (
<div>
<Link href="Dashboard">Go back to Dashboard</Link>
<button onClick={handleSignIn}>Click me</button>
</div>
);
};
export default SignIn;
And the content of the file views/Dashboard/index.tsx is:
import React, { useContext } from 'react';
import Link from 'next/link';
import { AuthContext } from '../../../contexts/auth';
const Dashboard: React.FC = () => {
const { signed, signIn } = useContext(AuthContext);
async function handleSignIn() {
signIn();
}
return (
<div>
<Link href="SignIn">Go back to sign in page</Link>
<button onClick={handleSignIn}>Click me</button>
</div>
);
};
export default Dashboard;
I am able to access the context inside both /Dashboard and /SignIn, but when I press the link, the state comes back to the initial one. I figured out that the whole provider is rerenderized and therefore the new state becomes the initial state, but I wasn't able to go around this issue in a "best practices manner".
If I put the provider inside _app.tsx, I can maintain the state when transitioning between pages, but I end up providing this state to the / route as well, which I am trying to avoid.
I was able to go around this by doing the following, but it really does not seem to be the best solution for me.
I removed the Providers from Pages/SignIn/index.tsx and Pages/Dashboard/index.tsx and used the following snippet for the _app.tsx file:
import React from 'react';
import { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { AuthProvider } from '../contexts/auth';
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const router = useRouter();
const AuthProviderRoutes = ['/SignIn', '/Dashboard'];
return (
<>
{AuthProviderRoutes.includes(router.pathname) ? (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
) : <Component {...pageProps} />}
</>
);
};
export default App;
Does anyone have a better solution?
with RTL how do i check the to='/login' in Link to be true since RTL library mainly grabs by testid or text.
current page testing
import React from 'react';
import Proptypes from 'prop-types';
import { Link } from 'react-router-dom';
function Navigation(props) {
return (
<nav className="header__nav">
<Link to="/login">
<button type="button" className="header__login">LOGIN</button>
</Link>
</nav>
);
}
test
describe('Navigation Content', () => {
test('clicking login button', () => {
const props = jest.fn();
const { getByTestId, getByText } = render (
<Navigation popUpHandler={props}/>, { wrapper: MemoryRouter }
);
expect((<Link>).toHaveAttribute('to', '/login')
})
})
page i am trying to render to when clicked
function LoginForm() {
return (
<div className="login">
<h1 className="entryheader__header">Login</h1>
</div>
)
export default LoginForm;
Could you try rendering your component like this:
import { MemoryRouter } from 'react-router-dom'
...
...
const { getByTestId, getByText } = render(<YourComponent {...props}/>, { wrapper: MemoryRouter });
fireEvent.click(getByText('LOGIN'));
expect(getByText('some-text-on-login-page')).toBeInTheDocument();
This will wrap your component with the memory router rather than you doing it manually.
You can also try using BrowserRouter instead of MemoryRouter in your test file..
I wish I had an answer to this question but I do feel like I can say the following:
I wouldn't do
<Link to="/login">
<button type="button" className="header__login">LOGIN</button>
</Link>
because this will render non-valid HTML markup. It breaks accessibility rules to start
hi react admin community,
I want to use logout button with custom component and router. I have checked documentation but not found any solution.
please suggest to me how I can use it.
below I added my code.
Adminroot Component
This is Admin Component.
import React from 'react';
import { Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import { UserList } from "../users/users";
import Dashboard from './dashboard';
import MyLogoutButton from '../auth/logout';
const authProvider = {
logout: params => Promise.resolve(),
};
function Adminroot(props) {
const dataProvider = jsonServerProvider('http://jsonplaceholder.typicode.com');
return (
<div>
<Admin logoutButton={MyLogoutButton} loginPage={false} dashboard={Dashboard} dataProvider={dataProvider}>
<Resource name="users" list={UserList} />
</Admin>
</div>
);
}
export default Adminroot;
MyLogoutButton Component
This component contains default code which provides react-admin for logout.
Now, when clicks on Logout button. by default redirects to /login Url.
There renders a Logout component (below written the logout component code) that contains logout logic and redirect to /signin but it shows blank page until I refresh the page.
import React, { forwardRef } from 'react';
import { useLogout } from 'react-admin';
import MenuItem from '#material-ui/core/MenuItem';
import ExitIcon from '#material-ui/icons/PowerSettingsNew';
const MyLogoutButton = forwardRef((props, ref) => {
const logout = useLogout();
const handleClick = () => logout();
return (
<MenuItem
onClick={handleClick}
ref={ref}
>
<ExitIcon /> Logout
</MenuItem>
);
});
export default MyLogoutButton;
Logout Component
import React , { useContext } from 'react';
import { AppContext } from '../../AppContext';
import { Redirect } from 'react-router-dom';
function Logout(props){
const {handleSignOut} = useContext(AppContext);
handleSignOut();
return props.history.push('/signin');
}
export default Logout;