Testing history.goback with testing-library and react - reactjs

I'm trying to check the goback navigation in this component:
class BackMore extends Component {
render() {
return (
<div className="backMore">
<div className="back" onClick={ this.props.history.goBack } data-testid="go-back">
<FontAwesomeIcon icon={faArrowLeft} />
</div>
<span className="title">{ this.props.title }</span>
<More/>
</div>)
}
}
export default withRouter(BackMore)
I use testing-library and the recipe from page https://testing-library.com/docs/example-react-router
// test utils file
function renderWithRouter(
ui,
{
route = '/',
history = createMemoryHistory({ initialEntries: [route] }),
} = {}
) {
const Wrapper = ({ children }) => (
<Router history={history}>{children}</Router>
)
return {
...render(ui, { wrapper: Wrapper }),
// adding `history` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
history,
}
}
And this is my test:
test('Go back in the history', () =>{
const browserHistory = createMemoryHistory()
browserHistory.push('/my-learning');
const { history } = RenderWithRouter(<BackMore />, { route: ['my-learning'], history: browserHistory });
userEvent.click(screen.getByTestId(/go-back/i));
expect(history.location.pathname).toBe('/')
})
The history.location.pathname variable is 'my-learning' and it should be '/'
What is it wrong?

Related

Could not find react-redux context value; please ensure the component is wrapped in a <Provider> although component is wrapped in a provider

I have a React on Rails app and I am getting a failure to render due to could not find react-redux context value; please ensure the component is wrapped in a <Provider>.
Component (inner content and imports omitted for brevity):
const MyPage = ({ reduxData, payloads }) => {
const store = useReduxStore(reduxData);
const jads = new JsonApiDataStore();
const users = jads.sync(payloads.users);
//custom selector
const pathName = useSelector(selectPathName);
return (
<Provider store={store}>
<div>
{users.map((user) => (
<li key={user.id} />
))}
</div>
<div>{pathname}</div>
</Provider>
);
};
MyPage.propTypes = {
reduxData: PropTypes.shape(ReduxData).isRequired,
payloads: PropTypes.shape({
users: PropTypes.shape(Payload).isRequired,
}).isRequired,
};
export default MyPage;
view(index.html.erb for this directory/controller)
<% content_for :title, "MyPage" %>
<% page = react_component_hash(
"MyPage",
props: {
reduxData: redux_data,
payloads: {
users: serialize(
#users,
UserSerializer,
),
}
},
prerender: true
) %>
<% content_for :page_styles, page["componentCss"] %>
<%= page["componentHtml"] %>
Controller Route:
def index
#users = User.all
render layout: "react"
end
in my application controller:
def redux_data
{
data: {
pathName: request.path
},
currentUser: serialize(current_user, CurrentUserSerializer, include: %w[company])
}
end
helper_method :redux_data
(This works on other pages with a similar setup).
What am I missing?
The issue was that I was trying to use a selector in the same component that provided the Provider / context, essentially before the context was provided.
Instead, I needed to use selectors only in child components / within the main component that has the <Provider /> wrapper so that the selector has access to the context.
In my case:
child component:
const ChildComponent = () => {
const pathName = useSelector(selectPathName);
return (
<div>{pathname}</div>
);
};
export default ChildComponent;
parent component:
const MyPage = ({ reduxData, payloads }) => {
const store = useReduxStore(reduxData);
const jads = new JsonApiDataStore();
const users = jads.sync(payloads.users);
return (
<Provider store={store}>
<div>
{users.map((user) => (
<li key={user.id} />
))}
</div>
<ChildComponent />
</Provider>
);
};
MyPage.propTypes = {
reduxData: PropTypes.shape(ReduxData).isRequired,
payloads: PropTypes.shape({
users: PropTypes.shape(Payload).isRequired,
}).isRequired,
};
export default MyPage;

jest react-testing-library error symbol is not a function

Hello everyone i have problem with simple test i try test has text in Link tag and has link tag in nav but i see error symbol is not a function can you help me please?
my test code:
const mockUseLocationValue = {
pathname: "",
search: '',
hash: '',
state: null
}
jest.mock('react-router', () => ({
...jest.requireActual("react-router") as {},
useLocation: jest.fn().mockImplementation(() => {
return mockUseLocationValue;
})
}));
test("should render the home page", () => {
render(<Header />);
const navbar = screen.getByTestId("navbar");
const link = screen.getByTestId("home-link");
expect(link.innerHTML).toMatch("Home page");
expect(navbar).toContainElement(link);
});
My original code:
export const Header = ():JSX.Element => {
const location = useLocation()
const { pathname } = location
const splitLocation = pathname.split('/')
return (
<HeaderBlock as='h3' block>
<nav data-testid="navbar">
<ul className={styles.links}>
<li>
<Link data-testid="home-link" to="/">
Home Page
</Link>
</li>
</ul>
</nav>
</HeaderBlock>
)
}
When using react-testing-library you can specify additional options for the render function, one of them being a wrapper.
wrapper
Pass a React Component as the wrapper option to have it rendered
around the inner element. This is most useful for creating reusable
custom render functions for common data providers.
You shouldn't mock functionality you are testing.
Create a memory router for testing in jest.
import { MemoryRouter } from 'react-router-dom';
export const RouterWrapper = ({ children }) => (
<MemoryRouter>{children}</MemoryRouter>
);
In test, import the wrapper and pass in the options.
import { RouterWrapper } from '../path/to/test/util';
test("should render the home page", () => {
render(
<Header />,
{
wrapper: RouterWrapper
}
);
const navbar = screen.getByTestId("navbar");
const link = screen.getByTestId("home-link");
expect(link.innerHTML).toMatch("Home page");
expect(navbar).toContainElement(link);
});

Update children's state in higher parent component

I need some help. I have a wrapper App component where I hold the state of the application, the App component renders a Router component. I need to set in every page of the application the changes that were made on the page. Every page has a next button to the next page using NavLinks.
I tried to pass down a handle change method but I can not get it to work, the handleChange doesn't get fired. Any help would be very much appreciated.
App:
export default class App extends React.Component {
state = {
categoriesWithSub: [],
currentPage: '',
ticket: {
departments: '',
request: '',
exactRequest: '',
attachments: '',
},
};
componentDidMount = async () => {
const categories = await getCategories();
const categoriesWithSub = await Promise.all(
categories.map(async category => {
const subCategories = await getSubCategories(category.id);
return { ...category, subCategories };
}),
);
this.setState({ categoriesWithSub });
};
makeHandleChange = () => {
alert('Test!');
/* this.setState({
ticket: { ...this.state.ticket, [pageName]: pageChanges },
}); */
};
render() {
const { categoriesWithSub } = this.state;
return <Router categoriesWithSub={categoriesWithSub} handleChange={this.makeHandleChange} />;
}
}
Router:
const routes = [
{
path: '/',
exact: true,
component: Home,
},
{
path: '/departments',
exact: true,
component: Departments,
},
{
path: '/request',
exact: true,
component: Request,
},
{
path: '/thank-you',
exact: true,
component: ThankYou,
},
{
path: '/success',
exact: true,
component: Success,
},
];
export default function Router(categoriesWithSub, handleChange) {
return (
<Switch>
{routes.map(({ path, exact, component: Component, layoutProps = {} }) => {
const WithLayout = ({ ...props }) => (
<Layout {...layoutProps}>
<Component {...props} />
</Layout>
);
return (
<Route
key={path}
path={path}
exact={exact}
render={() => <WithLayout categoriesWithSub={categoriesWithSub} handleChange={handleChange} />}
/>
);
})}
</Switch>
);
}
Child component:
export default class extends Component {
state = {
};
componentDidMount() {
this.props.handleChange(this.state);
}
render() {
const { categoriesWithSub } = this.props.categoriesWithSub;
return (
{categoriesWithSub &&
categoriesWithSub.map(item => (
<Tile
to="./request"
department={item.catName}
key={item.id}
/>
))}
</div>
);
}
}
export function Tile({ to, department }) {
return (
<NavLink to={to} css={tile}>
{department}
</NavLink>
);
}
This will not work
export default function Router(categoriesWithSub, handleChange)
That should be
export default function Router({categoriesWithSub, handleChange})
//or
export default function Router(props){
const {categoriesWithSub, handleChange} = props;
}

Code splitting route components wrapped in a HOC with React Loadable

I am running into problems using React Loadable with route based code splitting using Webpack 3.11.
When I try to render my app on the server my async modules immediately resolve without waiting for the promise. Thus the SSR output becomes <div id="root"></div>.
App.js:
const App = () => (
<Switch>
{routes.map((route, index) => (
<Route key={index} path={route.path} render={routeProps => {
const RouteComponent = route.component
return <RouteComponent {...routeProps} />
}} />
))}
</Switch>
)
I've defined my async route components with React Loadable like this:
Routes.js
function Loading ({ error }) {
if (error) {
return 'Oh nooess!'
} else {
return <h3>Loading...</h3>
}
}
const Article = Loadable({
loader: () => import(/* webpackChunkName: "Article" */ '../components/contentTypes/Article'),
loading: Loading
})
const Page = Loadable({
loader: () => import(/* webpackChunkName: "Page" */ '../components/contentTypes/Page'),
loading: Loading,
render (loaded, props) {
let Component = WithSettings(loaded.default)
return <Component {...props}/>
}
})
export default [
{
path: `/:projectSlug/:env${getEnvironments()}/article/:articleSlug`,
component: Article,
exact: true
},
{
path: `/:projectSlug/:env${getEnvironments()}/:menuSlug?/:pageSlug?`,
component: Page
}
]
WithSettings.js
export default (WrappedComponent: any) => {
class WithSettings extends React.Component<WithSettingsProps, WithSettingsState> {
static displayName = `WithSettings(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`
state = {
renderWidth: 1200
}
componentDidMount () {
this.loadSettings({ match: { params: { projectSlug: '', env: '' } } })
window.addEventListener('resize', this.onResize)
this.onResize()
}
componentWillUnmount () {
if (isClient) {
window.removeEventListener('resize', this.onResize)
}
}
componentDidUpdate (oldProps) {
this.loadSettings(oldProps)
}
onResize = () => {
this.setState({ renderWidth: this.getLayoutWidth() })
}
getLayoutWidth () {
return (document.body && document.body.offsetWidth) || 0
}
loadSettings (oldProps) {
const { settings, request, getNewSettings } = this.props
const { projectSlug: oldProjectSlug, env: oldEnv } = oldProps.match.params
const { projectSlug: newProjectSlug, env: newEnv } = this.props.match.params
if (
(
oldProjectSlug !== newProjectSlug ||
oldEnv !== newEnv
) ||
(
settings === undefined ||
(request.networkStatus === 'ready')
)
) {
getNewSettings()
}
}
render () {
const { settings, request, history, location, match } = this.props
const { renderWidth } = this.state
if (!settings || !request || request.networkStatus === 'loading') {
return <div />
}
if (request.networkStatus === 'failed') {
return <ErrorBlock {...getErrorMessages(match.params, 'settings')[4044]} fullscreen match={match} />
}
return (
<WrappedComponent
settings={settings}
settingsRequest={request}
history={history}
location={location}
match={match}
renderWidth={renderWidth}
/>
)
}
}
hoistNonReactStatic(WithSettings, WrappedComponent)
return connect(mapStateToProps, mapDispatchToProps)(WithSettings)
}
I've managed to narrow it down to the WithSettings HOC that I am using to wrap my route components in. If I don't use the WithSettings HOC (as with the Article route) then my SSR output waits for the async import to complete, and the server generated html includes markup related to the route (good!). If I do use the HOC (as with the Page route) then the module immediately resolves and my SSR output turns into <div id="root"></div because it no longer waits for the dynamic import to complete before rendering. Problem is: I need the WithSettings HOC in all my routes as it fetches required data from the server that I need to render the app.
Can anyone tell me how I can use a HOC and use React Loadable's async component for route components so that it works on the server?
Managed to figure it out.
It was due to my HOC. In the render method it would return <div /> when settings or request where undefined or request.networkStatus is loading. It turns out this tripped up server side rendering. Removing this block was enough to make it work.

Context in "stateless" component?

I have the following code in a component and I would like a stateless component to access this part of code:
Main component:
function createApp(store, communityIds) {
const App = React.createClass({
childContextTypes: {
localizedString: React.PropTypes.func,
},
getChildContext: function() {
return {
localizedString: function(key, fallback) {
return getKey(key, fallback);
},
};
},
render: function() {
return (
<Provider store={store}>
<Client communityIds={communityIds}/>
</Provider>
);
},
});
return <App/>;
}
Stateless:
export default () => (dispatch, getState) => {
const state = getState();
const token = state.user.get('token');
if (!token) {
throw new Error('test'); // this.context.localizedString does not work
}
}
What you have provided under your definition of "Stateless:" function is not what a Stateless function is. You have provided your action creator as a thunk. I assume you wanted to insert the code for your Client component instead. To access context in a stateless component, your Client component would do something like this (which is documented here)
const Client = (props, context) => {
return <div >{context.localizedString("someKey", "someFallback")} </div>
}
Client.contextTypes = {
localizedString: React.PropTypes.func
}
export default Client
I had the same question. The modern way (in 2019) is to use hook useContext(contextName). Docs: https://reactjs.org/docs/hooks-reference.html#usecontext
const dumbComp = (props) => {
const context = useContext(contextName);
return(
<div>
...
</div>
);
}
Use second parameter of stateless component
const MyStatelessComponent = (props, context) => {
const onGoButtonClick = () => {
context.router.push('https://www.google.co.in');
};
return(
<div>
<button onClick={() => onButtonClick()}>
{props.buttonName}
</button>
</div>
);
}
MyStatelessComponent.propTypes = {
buttonName: PropTypes.string.isRequired,
};
MyStatelessComponent.contextTypes = {
router: React.PropTypes.object.isRequired,
};
export default MyStatelessComponent;
Another solution is a self invoking function:
export default (Component=>(
Component.contextTypes = {
localizedString: React.PropTypes.func
}) && Component
)((props, context)=>{
return <div>{context.localizedString("someKey", "someFallback")}</div>
})
Or if you define the contextTypes separately to reuse it, you could do:
//addContextTypes.js
export default function(Component){
return (Component.contextTypes = {
localizedString: React.PropTypes.func
}) && Component
}
//component.jsx
import addContextTypes from './addContextTypes'
export default addContextTypes((props, context)=>{
return <div>{context.localizedString("someKey", "someFallback")}</div>
})
I had the same question, but was on Expo SDK 32, meaning I don't have access to hooks.
Here's how I achieved it:
import { reduxForm } from 'redux-form'
import { ReduxFormContext } from 'redux-form/lib/ReduxFormContext'
const ShowFormName = () => (
<ReduxFormContext.Consumer>
{
({ form }) => <Text>{form}</Text>
}
</ReduxFormContext.Consumer>
)
export default reduxForm({ form: 'formName' })(ShowFormName)

Resources