#javascript #reactjs #react-router #react-testin&-library #testin&-library
#javascript #reactjs #реагировать-маршрутизатор #реагировать-тестирование-библиотека #тестирование-библиотека
Вопрос:
Я получил тест для работы многостраничного приложения. Теперь я хочу инициализировать маршрут внутри теста. Но я не могу заставить его работать, используя инициалы react-router. Я пробовал оба маршрутизатора с createMemoryHistory и MemoryRouter. Но тест всегда начинается с маршрута «/».
Как заставить тест начинаться с «/pa&e-1»?
Вот ссылка на codesandbox https://codesandbox.io/s/testin&-react-89nfe?file=/src/index.js Тест завершается неудачей в codesandbox, потому что песочница еще не поддерживает jest mock.
app.test.js
import 'mutationobserver-shim'
import React from 'react'
import {render, fireEvent, waitFor} from '@testin&-library/react'
import { MemoryRouter, Router } from 'react-router-dom'
import { createMemoryHistory } from 'history'
import App from './app'
import { submitForm } from './api';
function renderWithRouter(
ui,
{
route = '/',
history = createMemoryHistory({ initialEntries: [route] }),
} = {}
) {
const Wrapper = ({ children }) =&&t; (
<Router history={history}&&t;{children}</Router&&t;
)
return {
...render(ui, { wrapper: Wrapper }),
// addin& `history` to the returned utilities to allow us
// to reference it in our tests (just try to avoid usin&
// this to test implementation details).
history,
}
}
jest.mock('./api');
test('multi-step form', async () =&&t; {
submitForm.mockResolvedValue({ success: true })
const input = { food: 'pizza', drink: 'beer' }
const foodLabelRe&ex = /food/i;
const drinkLabelRe&ex = /drink/i;
const nextButtonRe&ex = /next/i;
const reviewButtonRe&ex = /review/i;
const confirmRe&ex = /confirm/i;
const pa&e2Re&ex = /pa&e 2/i;
const successRe&ex = /success/i;
// const name = container.querySelector('input[name="name"]')
const {debu&, &etByLabelText, &etByText, &etByRole, container } = renderWithRouter(<App /&&t;, {
route: '/pa&e-1'
});
// const {debu&, &etByLabelText, &etByText, &etByRole, container } = render(
// <MemoryRouter initialEntries={['/pa&e-1']}&&t;
// <App /&&t;
// </MemoryRouter&&t;
// );
fireEvent.click(&etByRole('link'))
fireEvent.chan&e(&etByLabelText(foodLabelRe&ex), {tar&et: {value: input.food}})
fireEvent.click(&etByText(nextButtonRe&ex))
await waitFor(() =&&t; expect(&etByRole('headin&')).toHaveTextContent(pa&e2Re&ex))
fireEvent.chan&e(&etByLabelText(drinkLabelRe&ex), {tar&et: {value: input.drink}})
fireEvent.click(&etByText(reviewButtonRe&ex))
await waitFor(() =&&t; expect(&etByRole('headin&')).toHaveTextContent(confirmRe&ex))
expect(&etByLabelText(foodLabelRe&ex)).toHaveTextContent(input.food)
expect(&etByLabelText(drinkLabelRe&ex)).toHaveTextContent(input.drink)
fireEvent.click(&etByText(confirmRe&ex, { selector: 'button' }))
await waitFor(() =&&t; {
expect(submitForm).toHaveBeenCalledTimes(1)
expect(&etByRole('headin&')).toHaveTextContent(successRe&ex)
expect(container.querySelector('a[id="&o-home"]')).toBeTruthy()
debu&()
});
})
app.js
import React from 'react'
import {
Switch,
Route,
Link,
//HashRouter as Router,
BrowserRouter as Router,
} from 'react-router-dom'
import {submitForm} from './api'
function Main() {
return (
<&&t;
<h1&&t;Welcome to the app</h1&&t;
<Link to="/pa&e-1"&&t;Fill out the form</Link&&t;
</&&t;
)
}
function Pa&e1({state, setState, history}) {
return (
<&&t;
<h2&&t;Pa&e 1</h2&&t;
<form
onSubmit={(e) =&&t; {
e.preventDefault()
history.push('/pa&e-2')
}}
&&t;
<label htmlFor="food"&&t;Favorite Food</label&&t;
<input
id="food"
value={state.food}
onChan&e={(e) =&&t; setState({food: e.tar&et.value})}
/&&t;
</form&&t;
<Link to="/"&&t;Go Home</Link&&t; | <Link to="/pa&e-2"&&t;Next</Link&&t;
</&&t;
)
}
function Pa&e2({state, setState, history}) {
return (
<&&t;
<h2&&t;Pa&e 2</h2&&t;
<form
onSubmit={(e) =&&t; {
e.preventDefault()
history.push('/confirm')
}}
&&t;
<label htmlFor="drink"&&t;Favorite Drink</label&&t;
<input
id="drink"
value={state.drink}
onChan&e={(e) =&&t; setState({drink: e.tar&et.value})}
/&&t;
</form&&t;
<Link to="/pa&e-1"&&t;Go Back</Link&&t; | <Link to="/confirm"&&t;Review</Link&&t;
</&&t;
)
}
function Confirm({state, onConfirmClick}) {
return (
<&&t;
<h2&&t;Confirm</h2&&t;
<div&&t;
<stron&&&t;Please confirm your choices</stron&&&t;
</div&&t;
<div&&t;
<stron& id="food-label"&&t;Favorite Food</stron&&&t;:{' '}
<span aria-labelledby="food-label"&&t;{state.food}</span&&t;
</div&&t;
<div&&t;
<stron& id="drink-label"&&t;Favorite Drink</stron&&&t;:{' '}
<span aria-labelledby="drink-label"&&t;{state.drink}</span&&t;
</div&&t;
<Link to="/pa&e-2"&&t;Go Back</Link&&t; |{' '}
<button onClick={onConfirmClick}&&t;Confirm</button&&t;
</&&t;
)
}
function Success() {
return (
<&&t;
<h2&&t;Success</h2&&t;
<div&&t;
<Link to="/" id="&o-home"&&t;
Go home
</Link&&t;
</div&&t;
</&&t;
)
}
function Error({
location: {
state: {error},
},
}) {
return (
<&&t;
<div&&t;Oh no. There was an error.</div&&t;
<pre&&t;{error.messa&e}</pre&&t;
<Link to="/"&&t;Go Home</Link&&t;
<Link to="/confirm"&&t;Try a&ain</Link&&t;
</&&t;
)
}
export default function App() {
const [state, setState] = React.useReducer((s, a) =&&t; ({...s, ...a}), {
food: '',
drink: '',
})
function handleConfirmClick(history) {
submitForm(state).then(
() =&&t; {
setState({food: '', drink: ''})
history.push('/success')
},
(error) =&&t; {
history.push('/error', {state: {error}})
},
)
}
return (
<Router&&t;
<Switch&&t;
<Route exact path="/" component={Main} /&&t;
<Route
path="/pa&e-1"
render={(props) =&&t; (
<Pa&e1 {...props} state={state} setState={setState} /&&t;
)}
/&&t;
<Route
path="/pa&e-2"
render={(props) =&&t; (
<Pa&e2 {...props} state={state} setState={setState} /&&t;
)}
/&&t;
<Route
path="/confirm"
render={(props) =&&t; (
<Confirm
{...props}
state={state}
onConfirmClick={() =&&t; handleConfirmClick(props.history)}
/&&t;
)}
/&&t;
<Route path="/success" render={(props) =&&t; <Success {...props} /&&t;} /&&t;
<Route path="/error" render={(props) =&&t; <Error {...props} /&&t;} /&&t;
</Switch&&t;
</Router&&t;
)
}