MemoryRouter and jest test - react-router

https://reacttraining.com/react-router/web/guides/testing
The react-router testing documentation is bit obscure to me.
How to write a test to check a route is rendered
A Component. - APage.js
import React, { Component } from 'react'
export default class APage extends Component {
render() {
return (
<div>
A Page
</div>
)
}
}
Writing a unit test to check , as per documentation.
routes.test.js
import React from 'react'
import { render } from "react-dom";
import APage from './APage'
import {MemoryRouter} from 'react-router-dom';
test("render route", () => {
render(
<MemoryRouter initialEntries={["/apage"]}>
<APage />
</MemoryRouter>
);
});
It gives an error,
Invariant Violation: Target container is not a DOM element.
for render.
How do I write a basic test, like to test a component is rendered on a route.

I'd like to comment on Remi's solution, since the API in React Router v6 is a little different (and the link to the docs leads now to a 404):
import { Router } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
render(
<Router
location={history.location} // history.location has a default value of '/'
navigator={history}
>
<APage />
</Router>
);
})
see github repo here

I think you should use Router instead. Since that uses BrowserRouter. (see alternatives section on react router example page)
import { Router } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
history.push('/apage');
render(
<Router history={history}>
<APage />
</Router>
);
});
It could be that you should also add your page in a Route, but I'm not sure.
Then it would be something like:
import { Router, Route } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
history.push('/apage');
render(
<Router history={history}>
<Route path='/aroute' render={(props) => (<APage {...props} />)} />
</Router>
);
});

Ok. Route testing has to be done by enzyme. not just using jest.
Followed https://medium.com/#antonybudianto/react-router-testing-with-jest-and-enzyme-17294fefd303
Used enzyme mount to test.

Related

Uncaught Error: useRoutes() may be used only in the context of a <Router> component [duplicate]

I have installed react-router-domV6-beta. By following the example from a website I am able to use the new option useRoutes I have setup page routes and returning them in the App.js file.
After saving I am getting the following error:
Error: useRoutes() may be used only in the context of a component.
I am wondering If I am missing something here? I have created the pages inside the src/pages folder.
My code:
import { BrowserRouter, Link, Outlet, useRoutes } from 'react-router-dom';
// Pages
import Home from './pages/Home';
import About from './pages/About';
import Services from './pages/Services';
import Gallery from './pages/Gallery';
import Prices from './pages/Prices';
import Contact from './pages/Contact';
const App = () => {
const routes = useRoutes([
{ path: '/', element: <Home /> },
{ path: 'o-nama', element: <About /> },
{ path: 'usluge', element: <Services /> },
{ path: 'galerija', element: <Gallery /> },
{ path: 'cjenovnik', element: <Prices /> },
{ path: 'kontakt', element: <Contact /> }
]);
return routes;
};
export default App;
You should have a <BrowserRouter> (or any of the provided routers) higher up in the tree. The reason for this is that the <BrowserRouter> provides a history context which is needed at the time the routes are created using useRoutes(). Note that higher up means that it can't be in the <App> itself, but at least in the component that renders it.
Here's what your entry point could look like:
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'),
);
I think the problem is that you still need to wrap routes (Routes / useRoutes) inside a Router element.
So an example would look something like this:
import React from "react";
import {
BrowserRouter as Router,
Routes,
Route,
useRoutes,
} from "react-router-dom";
const Component1 = () => {
return <h1>Component 1</h1>;
};
const Component2 = () => {
return <h1>Component 2</h1>;
};
const App = () => {
let routes = useRoutes([
{ path: "/", element: <Component1 /> },
{ path: "component2", element: <Component2 /> },
// ...
]);
return routes;
};
const AppWrapper = () => {
return (
<Router>
<App />
</Router>
);
};
export default AppWrapper;
Refactor according to your needs.
its means in Your index js Or App JS wrap with BrowserRouter like this
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter> // Like This here I am using
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById("root"),
);
Mention below code in index.js
import { BrowserRouter as Router } from "react-router-dom";
Just want to report on a similar issue -- as of writing (v6.2.1), it seems you actually encounter this error as well if you are importing from react-router instead of react-router-dom. A costly typo on my part.
I.e., make sure you are importing Routes and Route from react-router-dom and NOT react-router
// This is deceptively valid as the components exist, but is not the intended usage
import { Routes, Route } from 'react-router';
// This works and is the intended usage
import { Routes, Route } from 'react-router-dom';
Code: index.js
import {BrowserRouter as Router} from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById("root")
);
app.js
function App() {
return (
<>
<Routes>
<Route path ="/" element={<Main />} />
<Route path ="gigs" element={<Gigs />} />
</Routes>
</>
);
}
Try to add your routes in index.js not in App.js. Your App.js is called from index.js. In the index.js your external page is called like this
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<Navbar />} />
</Routes>
</BrowserRouter>
</React.StrictMode>
);
> Following codes works since react-router-dom syntax changed because of React 18.
import logo from './logo.svg';
import './App.css';
import Header from './components/Header';
import Login from './components/Login';
import {
BrowserRouter as Router,
Routes,
Route,
useRoutes,
} from 'react-router-dom';
import Signup from './components/Signup';
function AppRoutes() {
const routes = useRoutes(
[
{path:'/',element:<Login/>},
{path:'/signup',element:<Signup/>}
]
)
return routes;
}
function App(){
return (
<Router>
<Header/>
<AppRoutes />
</Router>
)
}
export default App;
Try
const Routes = () => useRoutes([])
and then wrap it like this in App.js
<BrowserRouter>
<Routes />
</BrowserRouter>
It worked for me
I got this error because I had two different versions of react-router-dom being bundled.
If you're using npm/yarn workspaces, check that there is only one installed version of react-router-dom in the top-level node_modules folder

react-native-router-flux throws Actions is not defined

Trying to start with a super simple react-native-router-flux example using 4.0.0-beta.28. I receive the dreaded red screen that the Action is undefined. I am guessing I have something semantically incorrect?
Here's my code:
import React, {Component} from 'react'
import {Scene,Router} from 'react-native-router-flux';
import LoginScreen from '../shoppinglist/screens/login'
import LandingScreen from '../shoppinglist/screens/landing'
import {
Platform,
AppRegistry
} from 'react-native';
const MyApp = () => {
return (
<Router>
<Scene key={"root"}>
<Scene key="login" component={LoginScreen} title="Login">
</Scene>
<Scene key="home" component={LandingScreen} title="Home" initial></Scene>
</Scene>
</Router>
)
}
AppRegistry.registerComponent('shoppinglist', () => MyApp)
Code that triggers Action:
<Button onPress={() => Actions.home()} title={'Navigate to Login'} />
Simulator error
You're missing Actions from your import directive:
import { Actions, Scene, Router } from 'react-native-router-flux';

appbar responsive with options with react router v4, material-ui and apollo client

I'm working with apollo client, react, reac routerv4 and material-ui, my app is working ,
before insert material-ui i had
<Link to="/" className="navbar">React + GraphQL Tutorial</Link>
then i've inserted material-ui
<AppBar
title="Title"
iconClassNameRight="muidocs-icon-navigation-expand-more"
/>
but it's not clear for me how to add links for the title and options, in responsive mode with small screen the options i think must be invisible, in small screen not.
The official material-ui site is not well explained by example like bootstrap, so i need a litlle of help.
the full code is:
import React, { Component } from 'react';
import {
BrowserRouter,
Link,
Route,
Switch,
} from 'react-router-dom';
import './App.css';
import ChannelsListWithData from './components/ChannelsListWithData';
import NotFound from './components/NotFound';
import ChannelDetails from './components/ChannelDetails';
import AppBar from 'material-ui/AppBar';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import {
ApolloClient,
ApolloProvider,
createNetworkInterface,
toIdValue,
} from 'react-apollo';
const networkInterface = createNetworkInterface({ uri: 'http://localhost:4000/graphql' });
networkInterface.use([{
applyMiddleware(req, next) {
setTimeout(next, 500);
},
}]);
function dataIdFromObject (result) {
if (result.__typename) {
if (result.id !== undefined) {
return `${result.__typename}:${result.id}`;
}
}
return null;
}
// customResolvers:
// This custom resolver tells Apollo Client to check its cache for a Channel object with ID $channelId
// whenever we make a channel query. If it finds a channel with that ID in the cache,
// it will not make a request to the server.
const client = new ApolloClient({
networkInterface,
customResolvers: {
Query: {
channel: (_, args) => {
return toIdValue(dataIdFromObject({ __typename: 'Channel', id: args['id'] }))
},
},
},
dataIdFromObject,
});
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<BrowserRouter>
<MuiThemeProvider muiTheme={getMuiTheme()}>
<div className="App">
<Link to="/" className="navbar">React + GraphQL Tutorial</Link>
<AppBar
title="Title"
iconClassNameRight="muidocs-icon-navigation-expand-more"
/>
<Switch>
<Route exact path="/" component={ChannelsListWithData}/>
<Route path="/channel/:channelId" component={ChannelDetails}/>
<Route component={ NotFound }/>
</Switch>
</div>
</MuiThemeProvider>
</BrowserRouter>
</ApolloProvider>
);
}
}
export default App;
the right is add a code like this:
<AppBar position="static">
<Toolbar>
<IconButton color="contrast" aria-label="Menu">
</IconButton>
<Typography type="title" color="inherit" >
{"Admin"}
</Typography>
<AuthLink to="/customers" label="Customers"/>
<AuthLink to="/tours" label="Tours"/>
<AuthLink to="/signout" label="Sign Out"/>
<AuthLink to="/signin" label=" Sign In" whenLogged="false"/>
</Toolbar>
</AppBar>
Authlink is just a component that I wrote to show the options and where simple I add the Title to display options.
const AuthLink = (props) => {
let auth = checkAuth();
return (
( (auth && !props.whenLogged ) || (!auth && props.whenLogged == "false") ) ? (
<Link to={props.to} className="navbar"><Button>{props.label}</Button></Link>
) : (
null
)
);
}
"Button" is a component from material, "Link" from react-router, here the imports:
import {
BrowserRouter,
Link,
Route,
Switch,
Redirect,
} from 'react-router-dom';
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import IconButton from 'material-ui/IconButton';

Migrating from React Router v2 to v4

So, I'm currently using react-router v2 as follows:
import { IndexRoute, Router, Route, Redirect } from 'react-router';
import App from './components/App';
....
render () {
return (
<ApolloProvider store={store} client={client}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={PhotoGrid} />
<Route path="/view/:postId" component={Single}></Route>
<Route path="/login" component={LoginUser}></Route>
</Route>
</Router>
</ApolloProvider>
)
}
}
export default MainApp;
App.js
....
import Main from './Main';
const allPostsCommentsQuery = graphql(All_Posts_Comments_Query, {
options: {
cachePolicy: 'offline-critical',
fetchPolicy: 'cache-first',
},
});
function mapStateToProps(state) {
return {
auth: state.auth
};
}
export const mapDispatchToProps = (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
}
export default compose(
allPostsCommentsQuery,
connect(mapStateToProps, mapDispatchToProps)
)(Main);
Main.js
class Main extends React.Component {
constructor (props) {
super(props);
}
componentWillMount () {
if (!this.props.auth.token){
this.context.router.push('/login');
}
}
render () {
return (
<div>
<h1>
<Link to="/">Flamingo City</Link>
</h1>
{ React.cloneElement(this.props.children, this.props) }
</div>
);
}
}
Main.contextTypes = {
router: function() { React.PropTypes.func.isRequired }
};
export default Main;
How do I convert my current v2 router to v4? What I am not clear on, is the parent nested element:
<Route path="/" component={App}>
In all the v2 -> v4 conversion examples I have seen thus far, none clearly explain what happens to the child elements. Am I expected to place the child elements within the App.js component itself, and if so, in the version of my App.js, how would that work as the first sign of any navigation actually occurs with Main.js?
Really useful post on github where you can see all the important parts of migrating to v4.
https://gist.github.com/kennetpostigo/7eb7f30162253f995cd4371d85c1e196
Also explaining how to go about child routes. Basically, you are supposed to place a Match inside App.js so this parent component will become responsible for its own part of child routes, an so on with every parent component.
Haven't tried this, let me know how it goes!

React-Router 1.0 - 100% Client Side Routing - Page Refresh causes 404 error

I am creating a website for a client that will use strictly client side react-routing script.
Here is a sample of the router ....
import React from 'react';
import { Route } from 'react-router';
import { generateRoute } from '../utils/localized-routes';
export default (
<Route component={ require('../components/APP') }>
{ generateRoute({
paths: ['/', 'audience'],
component: require('../components/Audience')
}) }
{ generateRoute({
paths: ['speaker'],
component: require('../components/Speaker')
}) }
{ generateRoute({
paths: ['board'],
component: require('../components/Board')
}) },
{ generateRoute({
paths: ['questions'],
component: require('../components/parts/AskQuestion')
}) }
<Route path="*" component={ require('../pages/NotFound') } />
</Route>
);
With this being the code for generateRoute:
export function generateRoute({ paths, component }) {
return paths.map(function(path) {
const props = { key: path, path, component };
// Static `onEnter` is defined on
// component, we should pass it to route props
if (component.onEnter) props.onEnter = component.onEnter;
return <Route {...props} />;
});
}
Problem:
While I understand the Links will bypass server navigation and utilize transition to (client side), on page refresh, I get a "Page Cannot Be found".
If I manually put a hash tag before the browser's url input (myexample.com/#speaker), the page appears, but of course I cannot expect the user to do that.
If I must use hash tags to allow client side routing, where do I put them? I put them in the and/or the router, neither work.
Alternatively, can I achieve total client side routing w/o the ugly hash tags? If, so, how do I do it?
I'd much prefer a solution based on #3, but if all else fails I'll take a solution based on #2.
Any ideas?
Thanks in advance.
I could only find a solution using step #2 above and am stuck with hashtags.
import React from 'react';
import ReactDOM from 'react-dom';
import Router from 'react-router';
import createHistory from 'history/lib/createHashHistory'; <-- using this
// import createBrowserHistory from 'history/lib/createBrowserHistory';
const routerProps = {
routes: require('./router/routes'),
history: createHistory({ <--- added this to remove ugly querystring
queryKey: false
}),
createElement: (component, props) => {
return React.createElement(component, { ...props });
}
};
ReactDOM.render(
React.createElement(Router, { ...routerProps }),
document.getElementById('root')
);
Would still like to know how I can remove the hashtags completely with client-side routing.