#reactjs #react-router-dom
#reactjs #react-router-dom
Вопрос:
react-router-dom@^5.1.0:
version "5.1.2"
react@^16.12.0:
version "16.12.0"
"@testing-library/react@^11.1.2":
version "11.1.2"
history@^5.0.0:
version "5.0.0"
Простейший пример:
import React, {useEffect} from 'react';
import {render, act} from '@testing-library/react';
import {Router, useHistory, useLocation} from 'react-router-dom';
import {createMemoryHistory} from 'history';
test('Sample', async () => {
const Component = () => {
const location = useLocation();
const history = useHistory();
useEffect(() => {
const timeoutId = setTimeout(() => {
history.push('/abc');
}, 100);
return () => clearTimeout(timeoutId);
}, []);
console.log(history.location, location);
return (
<div>
<div>{history.location.search}</div>
<div>{location.search}</div>
</div>
);
};
const history = createMemoryHistory({
initialEntries: [
{
pathname: '/',
search: '',
},
],
});
const pushFn = jest.spyOn(history, 'push');
render(
<Router history={history}>
<Component />
</Router>
);
expect(pushFn.mock.calls.length).toEqual(0);
await act(() => new Promise(resolve => setTimeout(resolve, 125)));
expect(pushFn.mock.calls.length).toEqual(1);
expect(pushFn.mock.calls[0]).toEqual(['/abc']);
});
Эта console.log
строка перед history.push
вызовом выводится:
{ pathname: '/',
search: '',
hash: '',
state: null,
key: 'f2blf5di' } { pathname: '/',
search: '',
hash: '',
state: null,
key: 'f2blf5di' }
Хорошо, как и ожидалось.
Теперь, после history.push
вызова, он становится:
{ pathname: '/abc',
search: '',
hash: '',
state: null,
key: 'tz0v0qgf' } { action: 'PUSH',
location:
{ pathname: '/abc',
search: '',
hash: '',
state: null,
key: 'tz0v0qgf' } }
Итак … этот location
объект полностью изменяется, но history.location
по-прежнему имеет ту же структуру объекта.
Использование MemoryRouter
или BrowserRouter
исправляет это, но мне все равно нужно следить за history.push
вызовом, чтобы убедиться, что произошло только X количество нажатий.
Мне нужно выполнить эту проверку, потому что некоторые ошибки могут возникнуть из-за некоторых useEffect
в компоненте, где он запускается несколько раз и выполняет несколько history.push
с одинаковыми параметрами. В результате чего пользователям приходится нажимать кнопку возврата браузера (X 1) раз, чтобы вернуться туда, где они были.
Пробовал и это, но history.goBack()
местоположение вообще не меняется.
import React, {useEffect} from 'react';
import {render, act, screen, fireEvent} from '@testing-library/react';
import {BrowserRouter, useHistory, useLocation} from 'react-router-dom';
import window from 'global/window';
test('Sample', async () => {
const Component = () => {
const location = useLocation();
const history = useHistory();
useEffect(() => {
const timeoutId = setTimeout(() => {
history.push('/abc?foo=fooValue');
history.push('/abc?foo=fooValue');
history.push('/abc?foo=fooValue');
}, 100);
return () => clearTimeout(timeoutId);
}, []);
return (
<div>
<div>{history.location.search}</div>
<div>{location.search}</div>
{__DEV__ amp;amp; <button onClick={() => history.goBack()}>back</button>}
</div>
);
};
render(
<BrowserRouter>
<Component />
</BrowserRouter>
);
expect(window.location.pathname).toEqual('/');
expect(window.location.search).toEqual('');
await act(() => new Promise(resolve => setTimeout(resolve, 200)));
fireEvent.click(screen.getByRole('button', {name: 'back'}));
expect(window.location.pathname).toEqual('/abc');
expect(window.location.search).toEqual('?foo=fooValue');
fireEvent.click(screen.getByRole('button', {name: 'back'}));
fireEvent.click(screen.getByRole('button', {name: 'back'}));
fireEvent.click(screen.getByRole('button', {name: 'back'}));
fireEvent.click(screen.getByRole('button', {name: 'back'}));
fireEvent.click(screen.getByRole('button', {name: 'back'}));
expect(window.location.pathname).toEqual('/');
expect(window.location.search).toEqual('');
});
Как мне использовать MemoryRouter
или BrowserRouter
и следить за history.push
вызовом?
Или, может быть, как мне сделать Router
, чтобы не изменять структуру location
useLocation
объекта после history.push
?
[ОБНОВЛЕНИЕ] 15 ДЕКАБРЯ 2020 13:00
Потратив на это 2 дня, я просто соглашусь с этим решением:
const memoryRouterRef: {current: any} = React.createRef<any>();
render(
<MemoryRouter ref={memoryRouterRef}>
...
</MemoryRouter>
);
...do some stuff...
expect(memoryRouterRef.current.history.location.pathname).toEqual(...);
expect(memoryRouterRef.current.history.location.search).toEqual(...);
expect(memoryRouterRef.current.history.length).toEqual(...);
memoryRouterRef.current.history.goBack(); // This works now!!
// We can now check the pathname / search to make sure user can use the browser back button properly
expect(memoryRouterRef.current.history.location.pathname).toEqual(...);
expect(memoryRouterRef.current.history.location.search).toEqual(...);
В конце концов, я не думаю, что это работа для модульных тестов. Это должны быть просто тесты e2e. На данный момент, однако, этого достаточно для меня.
Обновление 11 апреля 2022 г.:
Вы можете сделать это чище, сделав это вместо этого:
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
const history = createMemoryHistory();
render(
<Router history={history}>
...
</Router>
);
Все остальное то же самое. Вы все еще можете это сделать expect(history.location.pathname).toEqual(...)
.
Некоторые различия вместо history.goBack()
, вы делаете history.back()
. Просто посмотрите history
документацию пакета о том, что доступно. Другой был бы history.length
. length
Свойства нет. Вместо этого вы должны просто проверить индекс, history.index
.