reach/router navigate not updating history object with react-testing-library - reactjs

The below test continues to fail. On button click it calls #reach/router navigate with a query string /?searchString=abcde however the history object is not updated in the test. How do you test programmatic navigation updates with react-testing-library and reach-router without mocking.
import { createHistory, createMemorySource, LocationProvider, navigate } from "#reach/router"
import { render, waitFor } from "#testing-library/react"
import userEvent from "#testing-library/user-event"
import React from 'react'
test('Can us search path', async () => {
const history = createHistory(createMemorySource('/'))
const page = render(
<LocationProvider history={history}>
<div>
<button onClick={() => navigate('/?searchString=abcde')}>navigateButton</button>
</div>)
</LocationProvider>
)
expect(history.location.search).toEqual('')
userEvent.click(page.getByText('navigateButton')) //click button to call navigate
await waitFor(() => expect(history.location.search).toEqual('searchString=abcde')) //FAILS but Should PASS
})
dependencies
"#testing-library/react": "^11.2.3",
"#testing-library/user-event": "^12.6.0",
"#testing-library/jest-dom": "^5.11.9",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"#reach/router": "^1.3.4",

Related

Previous routed component doesn't leave the DOM during react-router test

When I test the first case when a component is routed, and test another case, the previous case still appears in the test DOM.
After the test case : "renders properly" rendered a route to the Header component,
and the test case: "navigates to / on header title click" fails because the Header component is appearing more than once because the test DOM didn't clear the first case. I hope this image helps.
import * as React from 'react'
import {vi, describe, it, expect} from 'vitest'
import { fireEvent, waitFor} from '#testing-library/react'
import { Header } from "./Header"
import { renderWithRouter } from '../testHelpers'
vi.mock("./../CartWidget", ()=> ({
CartWidget: ()=> <div>Hello Widget</div>
}))
describe('Header Shared Component', ()=> {
it('renders properly', ()=> {
const {container} = renderWithRouter(()=><Header/>)
expect(container.innerHTML).toMatch('Goblin Store')
expect(container.innerHTML).toMatch('Hello Widget')
})
it('navigates to / on header title click', async()=> {
const {getByText, history} = renderWithRouter(() => <Header />);
fireEvent.click(getByText("Goblin Store"));
await waitFor (()=>expect(history.location.pathname).toEqual("/"))
})
})
// renderWithRouter
import * as React from 'react'
import * as ReactDom from 'react-router-dom';
import {createMemoryHistory, MemoryHistory} from 'history'
import {render, RenderResult} from '#testing-library/react'
const {Router} = ReactDom;
export const renderWithRouter: RenderWithRouter = (renderComponent, route)=>{
const history = createMemoryHistory()
if(route){
history.push(route)
}
return {
...render(
<Router history={history}>
{renderComponent()}
</Router>),
history
}
}
type RenderWithRouter = (
renderComponent: ()=> React.ReactNode,
route?: string
)=> RenderResult & { history: MemoryHistory }
export const Header = () => (
<header>
<div className="container">
<div className="nav-brand">
<Link to="/">Goblin Store</Link>
<p>Everything for your Typescript adventure</p>
</div>
...
</div>
</header>
);
I have read the react router official DOC with testing for react-router-dom v5, but it appears to be same on my end.
Here are my dependencies
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^5.3.4",
"#testing-library/jest-dom": "^5.16.5",
"#testing-library/react": "^13.4.0",
"vite": "^4.0.0",
"vitest": "^0.26.3"

IonContent not rendering under IonicPage. React. No error

EDIT: I believe the reason that I ran into this problem is I didn't use the ionic start command to create my project and because I didn't have the Ionic CDN in my HTML file.
I hope I can explain this issue I'm having in a clean and concise way.
Environment Information
I am currently using Ionic 6 with React 18
And here are my Dependencies:
"dependencies": {
"#ionic/react": "^6.1.2",
"#ionic/react-router": "^6.1.2",
"#testing-library/jest-dom": "^5.16.4",
"#testing-library/react": "^13.1.1",
"#testing-library/user-event": "^13.5.0",
"#types/jest": "^27.4.1",
"#types/node": "^16.11.27",
"#types/react": "^18.0.6",
"#types/react-dom": "^18.0.2",
"axios": "^0.26.1",
"bootstrap": "^5.1.3",
"dart-sass": "^1.25.0",
"node": "16.14.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router": "^5.3.1",
"react-router-dom": "^5.3.1",
"react-scripts": "5.0.1",
"reactstrap": "^9.0.2",
"testcafe-react-selectors": "^4.1.5",
"typescript": "^4.6.3",
"web-vitals": "^2.1.4",
"xlsx": "^0.18.5"
},
"devDependencies": {
"#types/jest": "^27.4.1",
"#types/node": "17.0.24",
"#types/react": "^18.0.5",
"#types/react-dom": "^18.0.1",
"#types/react-router": "^5.1.18",
"#types/react-router-dom": "^5.3.3",
"#typescript-eslint/eslint-plugin": "^5.19.0",
"#typescript-eslint/parser": "^5.19.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.5.1",
"prettier": "^2.6.2",
"react-scripts": "^5.0.1",
"start-server-and-test": "^1.14.0",
"testcafe": "^1.18.6",
"typescript": "4.6.3",
"typescript-plugin-css-modules": "^3.4.0"
},
Goal
I want to be able to have an Ionic React multi-page application. Obviously it will still be a SPA and not actually have different pages but I want to utilize Ionic's IonPage component which creates a new React View that can be navigated to.
Expected Results
I've stripped much of my project to just bare bones components that I want to be able to use to create a side navigation and be able to navigate to each page and see the contents. Currently I'm just trying to get the ability to see everything from a React Component wrapped in IonPage. This is my App.tsx page:
App.tsx
import { IonApp, IonRouterOutlet, setupIonicReact } from '#ionic/react';
import { IonReactRouter } from '#ionic/react-router';
import React from 'react';
import { Route } from 'react-router';
import Home from './components/Home';
import './custom.css';
setupIonicReact({
mode: 'md',
});
function App() {
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route path="/" component={Home} exact={true} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
);
}
export default App;
I've tried a variety of layouts for my Home.tsx file. Currently this is what is inside
First situation
Home.tsx Current Version
import { IonContent, IonHeader, IonPage } from '#ionic/react';
import React from 'react';
const Home: React.FC = () => {
return (
<IonPage>
<IonHeader>Example</IonHeader>
<IonContent fullscreen>
<h1>Test</h1>
</IonContent>
</IonPage>
);
};
export default Home;
With this setup, the Example Header text shows and is styled like a header correctly. However, the Content Text doesn't show up at all on this. I've determined the reason why it doesn't show up is because the ion-content element/component's height is set to 0. This is not expected. We don't have any css in our project targeting ion-content elements.
Second situation
import { IonContent, IonPage } from '#ionic/react';
import React from 'react';
const Home: React.FC = () => {
return (
<IonPage>
<IonContent fullscreen>
<h1>Test</h1>
</IonContent>
</IonPage>
);
};
export default Home;
In this situation I expect the elements inside of the IonContent Component to be rendered. I originally didn't have the 'fullscreen' attribute but added it recently to test if it was different. It didn't have an affect. What the result is is a completely blank page with the ion-page having width of 0 as well as the ion-content having a width of 0. If I set the widths of the ion-page manually it appears the ion-content's width and height update as well and it shows the content I was looing for.
Third Situation
import { IonContent } from '#ionic/react';
import React from 'react';
const Home: React.FC = () => {
return (
<IonContent fullscreen>
<h1>Test</h1>
</IonContent>
);
};
export default Home;
In this situation I've removed the IonPage element from the page. Surprisingly it appears that the content is displayed in this situation even though I've seen online that the IonRouter need to find an element with IonPage.
What I want to achieve:
I want to be able to have a react component with IonPage, IonHeader, and IonContent and have all elements inside of these render on the page when navigated to it.
What I want to know
What is causing the IonPage to behave strangely by having 0 height at some points. Why does the Content not show when wrapped around an IonPage but an IonHeader wrapped around an IonPage does show.
No Error Messages Available
Additional Notes
I also attempted to create a new IonicProject with react 17 instead of 18 as I thought it was a support issue, but it appears with the same full code with IonPage, IonContent, and IonHeader the content was not displaying. Thank you for your time!
I basically created a sidenav template from ionic start and determined all of the differences between the two projects.
I eventually determined that there are additional css import statements that I didn't realize I needed to add to my App.tsx in order for the IonApp,IonHeader,IonContent to work correctly.
These are the imports I found from the template project:
/* Core CSS required for Ionic components to work properly */
import "#ionic/react/css/core.css";
/* Basic CSS for apps built with Ionic */
import "#ionic/react/css/normalize.css";
import "#ionic/react/css/structure.css";
import "#ionic/react/css/typography.css";
/* Optional CSS utils that can be commented out */
import "#ionic/react/css/padding.css";
import "#ionic/react/css/float-elements.css";
import "#ionic/react/css/text-alignment.css";
import "#ionic/react/css/text-transformation.css";
import "#ionic/react/css/flex-utils.css";
import "#ionic/react/css/display.css";
/* Theme variables */
import "./theme/variables.css";
The theme variables are option and if there isn't a variables.css file in the theme folder it will give an error.
However after adding these import statements all of the content on the page renders.

Why is this React component page not rendering correctly when an id is passed via the React Router URL?

I'm using the following dependencies for React:
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-helmet": "^6.1.0",
"react-html-parser": "^2.0.2",
"react-icons": "^3.11.0",
"react-live-clock": "^4.0.5",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.3"
I have a menu where I load a component PageHowtos with and without a passed id:
<Route exact path="/howtos" render={() => <PageHowtos {...this.state.pageProps} />} />
<Route exact path="/howtos/:id" render={() => <PageHowtos {...this.state.pageProps} />} />
When I don't send an id in the URL, the page renders fine:
But when I send an id in the URL, the page receives the id, but it doesn't render correctly:
This is the PageHowtos.tsx page:
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import 'custom/styles/pageHowtos.scss';
const pageTitle = 'Web Developer > Howtos';
interface IProps {
changeSiteTitle: any
}
interface ParamTypes {
id: string
}
function PageHowtos(props: IProps) {
const { id } = useParams<ParamTypes>();
useEffect(() => {
props.changeSiteTitle(pageTitle);
}, []);
return (
<div className="page_howtos">
<Helmet>
<title>Howtos</title>
</Helmet>
<div>
This is the Howtos page. From URL, id = {id}
</div>
</div>
)
}
export default PageHowtos;
What could be causing the page not to render correctly when an id is passed in the URL?
ADDENDUM
It seems that when the id is passed, the page does not load the Bootstrap styles (!). Why could that be?
I found the answer. It had nothing to do with React-Router as such.
I was loading my Bootstrap files with a relative path and needed a preceding slash!

Zoom-Scrolling not Working in Pigeon-Maps ReactJS

I have a problem with pigeon-maps in my reactjs-app. Using the mouse to navigate through the map works fine, but zooming with the scroll wheel does not work. However, a double-click zooms in the map, but then i have no way of zooming out again.
zoom and center are controlled variables.
<Map
limitBounds='edge'
center={center}
onBoundsChanged={res => {setCenter(res.center); setZoom(res.zoom)}}
zoom={zoom}
provider={provider}
onClick={changeLocation}
width={width}
height={height}
zoomOnMouseWheel={true}
animate={true}>
{renderMarkers()}
</Map>
Once rendered the component looks like this:
These are the dependencies i am using:
"dependencies": {
"#types/jest": "^25.1.4",
"#types/node": "^13.9.0",
"#types/react": "^16.9.23",
"#types/react-dom": "^16.9.5",
"axios": "^0.19.0",
"classnames": "^2.2.6",
"pigeon-maps": "^0.14.0",
"pigeon-overlay": "^0.2.3",
"react": "^16.11.0",
"react-cookie": "^4.0.3",
"react-dom": "^16.11.0",
"react-redux": "^7.1.3",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.3.1",
"react-spring": "^8.0.27",
"react-use-clipboard": "1.0.2",
"reactjs-popup": "^1.5.0",
"redux": "^4.0.5",
"sockjs-client": "^1.4.0",
"stompjs": "^2.3.3",
"typescript": "^3.8.3"
}
If any of you have an Idea what might be causing my problem, i would be thankful if you shared it with me.
Best regards,
Koenigstein
edit: the whole code :
import React, {useEffect, useState} from 'react'
import Map from "pigeon-maps";
import "./editor.css";
export default function EditorInterface(props) {
const [selectedLocation, setSelectedLocation] = useState([0,0]);
const defaultWidth = window.innerWidth * 0.6;
const defaultHeight = window.innerHeight * .50 - 24;
const [zoom, setZoom] = useState(13);
const [width, setWidth] = useState(defaultWidth);
const [height, setHeight] = useState(defaultHeight);
const [center, setCenter] = useState([49.750049, 6.637275]);
const provider = (x, y, z) => {
const s = String.fromCharCode(97 + (x + y + z) % 3);
return `https://${s}.tile.openstreetmap.org/${z}/${x}/${y}.png`
};
const changeLocation = (e) =>{
setSelectedLocation(e.latLng);
};
return (
<div className="wrapper">
<div>
<Map
limitBounds='edge'
center={center}
onBoundsChanged={res => {setCenter(res.center); setZoom(res.zoom)}}
zoom={zoom}
provider={provider}
onClick={changeLocation}
width={width}
height={height}
zoomOnMouseWheel={true}
animate={true}>
</Map>
</div>
</div>
);
};
The component is being used here:
import React from 'react';
import {
BrowserRouter as Router,
Route} from "react-router-dom";
import AdminInterface from "./components/admininterface/AdminInterface";
import Watermark from "./components/watermark/Watermark";
import UserInterface from "./components/userinterface/UserInterface";
import EditorInterface from "./components/editorinterface/EditorInterface"
const App = () => {
return (
<Router>
<Route exact path={"/"} render={() =>
<UserInterface/>
}/>
<Route path={"/admin"} render={() =>
<AdminInterface/>
}/>
<Route path={"/scenarioCreator"} render={() =>
<EditorInterface/>
}/>
<Watermark/>
</Router>
);
};
export default App;
I will close this for now, because as DILEEP THOMAS pointed out, there is nothing wrong with the code, there seems to be something wrong with my project configuration or something else.
I will try to figure it out myself first.
According to pigeon-maps docs, you need to press (cmd/win) key while zooming with the mouse wheel
metaWheelZoom - Zooming with the mouse wheel only works when you hold down the meta (cmd/win) key. Defaults to false.
Docs here : https://github.com/mariusandra/pigeon-maps

Two Datepickers from formik-material-ui-pickers collide

I have a Formik form where I have two Datepickers from formik-material-ui-pickers
import {Field, Form} from "formik";
import React from "react";
import Button from "#material-ui/core/Button";
import { TextField } from "formik-material-ui";
import { DatePicker } from "formik-material-ui-pickers";
import InputAdornment from "#material-ui/core/InputAdornment";
import "./drillForm.css"
export function DrillForm(props) {
return (
<Form className="drillForm" onSubmit={props.handleSubmit}>
<div className="fieldSet">
<DatePicker label="FabricacĂ­on" name="built" />*
<DatePicker label="GarantĂ­a" name="warranty" />
</div>
</Form> );
}
If I remove any of them the remaining Datepicker work like a charm but if I try to use them together they race into this repeating error
index.js:1 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
in PickerWithState (created by FormikMaterialUIDatePicker)
in FormikMaterialUIDatePicker (at drillForm.jsx:23)
in div (at drillForm.jsx:21)
in div (at drillForm.jsx:12)
in form (created by Form)
...
This is the component where I get the data and render the Form
import React, { useEffect } from "react";
import { useHistory} from "react-router";
import { useDispatch, useSelector } from "react-redux";
import { Formik } from "formik";
import {actions as drillActions} from "../../../ducks/drills";
import CircularProgress from "#material-ui/core/CircularProgress";
import {DrillForm} from "../../elements/forms/drillForm";
import Paper from "#material-ui/core/Paper";
import "./drillEdit.css"
import moment from "moment";
export default function ({idx}) {
const history = useHistory();
const dispatch = useDispatch();
const {username, token} = useSelector(({user})=> user);
const [drill, updating, errors] =
useSelector(({drills})=> [drills.drills[idx], drills.update, drills.errors]);
useEffect(
() => {
dispatch(drillActions.get(username, token, idx));
}, []
);
console.log("form Drill", {...drill});
return (
<Paper className="editForm">
<div>
<h1>{drill?drill.name:"Collecting Data"}</h1>
<h2>{drill?"Edit your drill " : "It may take few seconds"}</h2>
</div>
<div>
{drill?
<Formik
initialValues={drill}
initialErrors={errors}
onSubmit={(values, actions) => {
dispatch(drillActions.update(username, token, values));
actions.setSubmitting(false);
}}
>
{formikProps => <DrillForm {...formikProps} history={history}
updating={updating}/>}
</Formik>
: <CircularProgress/>
}
</div>
</Paper>);
These are my dependencies:
"#material-ui/core": "^4.8.3",
"#material-ui/icons": "^4.5.1",
"#material-ui/styles": "^4.8.2",
"#material-ui/pickers": "^3.2.10",
"#reduxjs/toolkit": "^1.2.3",
"formik": "^2.1.2",
"formik-material-ui": "^2.0.0-alpha.2",
"formik-material-ui-pickers": "^0.0.4",
"material-ui-popup-state": "^1.5.1",
"moment": "^2.24.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^7.1.3",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-observable": "^1.2.0",
"remote-redux-devtools": "^0.5.16",
"#date-io/moment": "1.3.13",

Resources