Как шпионить за `историей` с помощью `BrowserRouter` или `MemoryRouter`?

#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 .