Testing components using next-translate and i18n - reactjs

In our Next.js app we use vinissimus/next-translate package for translations.
We set it up accordingly:
next.config.js:
const nextTranslate = require('next-translate')
module.exports = nextTranslate()
i18n.js config file:
{
"locales": ["en"],
"defaultLocale": "en",
"pages": {
"*": ["en"],
"/": ["en"],
}
}
Using it inside the component with useTranslation hook:
const App = () => {
const { t, lang } = useTranslation();
return (
<Homepage>
<span>{t(`${lang}:homepage.title.header`)}</span>
</Homepage>
My question is how can I test it with jest with react-testing-library?
When I'm rendering my component in tests the translations do not work, only the key is returned. Also, I'd like to test it by getByText / findByText and passing:
{t(`${lang}:homepage.header`)}
How can I setup some kind of a wrapper or config for tests if I'm not using any i18nProvider in app but only this i18n.js config?

Here is an example wrapper:
// tests/wrapper.js
import { render } from '#testing-library/react'
import I18nProvider from 'next-translate/I18nProvider'
import commonEN from '../locales/en/common.json'
const Providers = ({ children }) => (
<I18nProvider
lang={'en'}
namespaces={{
common: commonEN
}}
>
{children}
</I18nProvider>
)
const customRender = (ui, options = {}) => render(ui, { wrapper: Providers, ...options })
export * from '#testing-library/react'
export { customRender as render }
A test would look like
// App.spec.js
import React from 'react'
import { render } from './tests/wrapper'
import App from './App'
jest.mock('next/router', () => ({
useRouter() {
return {
asPath: '/'
}
}
}))
describe('App', () => {
it('should render the app', async () => {
const res = render(<App />)
})
})

Related

Snapshot Testing With Jest, React and Redux

I'm trying to create a snapshot of my component which is using some custom useSelector and useDispatch hooks my boss created.
import { createDispatchHook, createSelectorHook } from "react-redux";
const Context = React.createContext(null);
export default Context;
export const useDispatch = createDispatchHook(Context);
export const useSelector = createSelectorHook(Context);
In my component the useSelector & useDispatch hooks are being called so I used jest.mock() on the hooks but then I get thrown an error saying TypeError: (0 , _reactRedux.createDispatchHook) is not a function. I can't find any documentation on how to mock a custom hook or how to even fix this issue.
import React, { createContext } from 'react';
import renderer from 'react-test-renderer';
import DecisionSidebar from './DecisionSidebar';
import { cleanup } from '#testing-library/react';
jest.mock('react-redux', () => ({
useDispatch: () => { },
useSelector: () => ({
project: {
myId: 0,
isProjectAdmin: true,
}
}),
}));
afterEach(cleanup);
describe('DecisionSidebar Snapshot Test', () => {
it('renders correctly with data', () => {
const component = renderer.create(<DecisionSidebar />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
I have also tried this in my jest file which produces a different error (which I have tried to fix since this one is easier and there are a lot of fixes, but still no luck)
const mockContext = React.createContext(null);
const mockCreateDispatchHook = () => new createDispatchHook()
const mockCreateSelectorHook = () => new createSelectorHook();
jest.mock('react-redux', () => ({
...jest.requireActual("react-redux"),
useSelector: () => mockCreateSelectorHook(mockContext),
useDispatch: () => mockCreateDispatchHook(mockContext),
}));
Using the the way from the redux website as suggested
import React from "react";
import { render } from "#testing-library/react";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import rootReducer from 'reducers';
const Context = React.createContext(null);
const renderer = (
ui,
{
initialState,
store = createStore(rootReducer, initialState, applyMiddleware(thunk)),
...renderOptions
} = {}
) => {
const Wrapper = ({ children }) => {
return (
<Provider
store={store}
context={Context}
>
{children}
</Provider>
);
}
return render(ui, { wrapper: Wrapper, ...renderOptions });
};
export * from "#testing-library/react";
export { renderer }
describe('DecisionSidebar Snapshot Test', () => {
it('renders correctly with data', () => {
const component = renderer(<DecisionSidebar />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
Still produces the same error

Dom does is not updated while testing redirection with react-testing library and react-router v6

I'm trying to test the redirection page when the user clicks on the button (I don't want to user jest.mock()).
I created the wrapper according to test-react-library documentation:
import { FC, ReactElement, ReactNode } from "react";
import { render, RenderOptions } from "#testing-library/react";
import { BrowserRouter } from "react-router-dom";
import userEvent from "#testing-library/user-event";
const WrapperProviders: FC<{ children: ReactNode }> = ({ children }) => {
return <BrowserRouter>{children}</BrowserRouter>;
};
const customRender = (
ui: ReactElement,
{ route = "/" } = {},
options?: Omit<RenderOptions, "wrapper">
) => {
window.history.pushState({}, "Home Page", route);
return {
user: userEvent.setup(),
...render(ui, { wrapper: WrapperProviders, ...options })
};
};
export * from "#testing-library/react";
export { customRender as render };
export type RenderType = ReturnType<typeof customRender>;
HomePage.tsx:
import { useNavigate } from "react-router-dom";
export default function HomePage() {
const navigate = useNavigate();
const handleClick = () => navigate("/other");
return (
<>
<h3>HomePage</h3>
<button onClick={handleClick}>Redirect</button>
</>
);
}
Other.tsx:
export default function Other() {
return <h3>Other</h3>;
}
HomePage.test.tsx:
import { render, RenderType } from "./customRender";
import HomePage from "./HomePage";
import "#testing-library/jest-dom/extend-expect";
describe("HomePage", () => {
let wrapper: RenderType;
beforeEach(() => {
wrapper = render(<HomePage />, { route: "/" });
});
test.only("Should redirects to other page", async () => {
const { getByText, user } = wrapper;
expect(getByText(/homepage/i)).toBeInTheDocument();
const button = getByText(/redirect/i);
expect(button).toBeInTheDocument();
user.click(button);
expect(getByText(/other/i)).toBeInTheDocument();
});
});
When I run the test, it fails and the page is not updated in the dom.
Does jest-dom does not support the re-render of the page and update the DOM? Or this test out of scope of the testing-library ?
From the code I can see that you are just rendering the HomePage component and inside of that component you don't have any logic that renders a new component based on the route changes (I suppose that you have that logic on another component). That's why when you click on the button you are not seeing the Other component rendered.
In this case I would suggest you to only make the assertions you need on the window.location object. So after you simulate the click on the button you can do:
expect(window.location.pathname).toBe("/other");
I updated the custom render and add a history for it:
const WrapperProviders: FC<{ children: ReactNode }> = ({ children }) => {
return <BrowserRouter>{children}</BrowserRouter>;
};
const customRender = (
ui: ReactElement,
{ route = "/",
history = createMemoryHistory({initialEntries: [route]}),
} = {},
options?: Omit<RenderOptions, "wrapper">
) => {
return {
user: userEvent.setup(),
...render( <Router location={history.location} navigator={history}>
ui
</Router>
, { wrapper: WrapperProviders, ...options })
};
};
and the test now passes:
describe("HomePage", () => {
let wrapper: RenderType;
beforeEach(() => {
wrapper = render(<HomePage />, { route: "/" });
});
test.only("Should redirects to other page", async () => {
const { getByText, user, history } = wrapper;
expect(getByText(/homepage/i)).toBeInTheDocument();
const button = getByText(/redirect/i);
expect(button).toBeInTheDocument();
await user.click(button);
expect(history.location.pathname).toBe('/other');
});

Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry. while using wrapper.html()

I am implementing unit testing, with jest & enzyme for react project. I am using react-intl for multiple language support.
My basic unit test code is
import React from 'react';
import MobileRechargeComponent from './';
import { shallowWithIntl, mountWithIntl } from '../../../../setupTestsHelper';
const wrapper = shallowWithIntl(<MobileRechargeComponent />);
describe('Title', () => {
it("should render initial layout", () => {
expect(wrapper.getElements()).toMatchSnapshot();
});
it('renders master components properly', () => {
console.log('wrapper>>>>>>>>>>>>>>>>>', wrapper.html())
expect(wrapper.length).toEqual(1);
});
});
I am getting error as like in following picture
My setupTestsHelper file code is as below
import React from 'react';
import { IntlProvider, intlShape, createIntl } from 'react-intl';
import { mount, shallow } from 'enzyme';
import { getCurrentLanguage } from './Lang';
const LocalLanguage = {
french: {},
arabic: {},
english: {}
}
const lang = getCurrentLanguage('en', LocalLanguage)
const intl = createIntl({ locale: 'en', lang }, {});
const nodeWithIntlProp = (node) => {
return React.cloneElement(node, { intl });
}
export const shallowWithIntl = (node) => {
return shallow(nodeWithIntlProp(node), { context: { intl } });
}
export const mountWithIntl = (node) => {
return mount(nodeWithIntlProp(node), {
context: { intl },
childContextTypes: { intl: intlShape }
});
}
it needs to implement custom intl function, which connects to intl object of react-intl.
For this, add this function in your setupTestsHelper file
import { IntlProvider } from 'react-intl';
const messages = require('./Lang/en.json') // en.json
const defaultLocale = 'en'
const locale = defaultLocale
export const intl = (component) => {
return (
<IntlProvider
locale={locale}
messages={messages}
>
{React.cloneElement(component)}
</IntlProvider>
);
}
And use in it in *.test.js file like below
import { intl } from '../../../../setupTestsHelper';
const wrapper = mount(intl(<MyComponent />));

Mocking Auth0Client in #auth0 in jest

I'm trying to mock out the import { Auth0Provider } from "#auth0/auth0-react"; in a react app and keep running into issues with this error [Error: For security reasons, window.crypto is required to run auth0-spa-js.] From what I can tell this error is coming from the Auth0Client which gets imported from import { Auth0Client } from '#auth0/auth0-spa-js'; My thought is to mock out the scoped module #auth0/auth0-spa-js and do something like this
const handleRedirectCallback = jest.fn(() => ({ appState: {} }));
const buildLogoutUrl = jest.fn();
const buildAuthorizeUrl = jest.fn();
const checkSession = jest.fn();
const getTokenSilently = jest.fn();
const getTokenWithPopup = jest.fn();
const getUser = jest.fn();
const getIdTokenClaims = jest.fn();
const isAuthenticated = jest.fn(() => false);
const loginWithPopup = jest.fn();
const loginWithRedirect = jest.fn();
const logout = jest.fn();
export const Auth0Client = jest.fn(() => {
return {
buildAuthorizeUrl,
buildLogoutUrl,
checkSession,
handleRedirectCallback,
getTokenSilently,
getTokenWithPopup,
getUser,
getIdTokenClaims,
isAuthenticated,
loginWithPopup,
loginWithRedirect,
logout,
};
});
This seems to be working if I import the Auth0Client into any of my tests, but the problem is that the Auth0Provider is still importing the non mocked out client. Is there anyway to get the Auth0Provider to import the mocked out Auth0Client instead of the actual implementation? The file that uses the Auth0Provider looks like this
// test-utils.js
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import { MockedProvider } from '#apollo/client/testing';
import { Auth0Provider } from "#auth0/auth0-react";
import { Auth0Client } from '#auth0/auth0-spa-js';
// Import your own reducer
import applicationsReducer from "../app/slices/applications.slice";
import { UserProvider } from "../user-context";
import {getUserMock} from "../__tests__/apollo_mocks/get_user.mock"
// const MockedAuth0Provider = jest.requireActual("#auth0/auth0-react").Auth0Provider
function render(
ui,
{
initialState,
mocks,
store = createStore(applicationsReducer, initialState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Auth0Provider clientId="__test_client_id__" domain="__test_domain__">
<Provider store={store}>
<MockedProvider mocks={[getUserMock, ...mocks]} addTypename={false}>
<UserProvider>{children}</UserProvider>
</MockedProvider>
</Provider>
</Auth0Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
Hopefully this helps someone else but I ended up mocking the provider as well as some of the other auth0 modules that I was using this way
jest.mock('#auth0/auth0-react', () => ({
Auth0Provider: ({ children }) => children,
withAuthenticationRequired: ((component, _) => component),
useAuth0: () => {
return {
isLoading: false,
user: { sub: "foobar" },
isAuthenticated: true,
loginWithRedirect: jest.fn()
}
}
}));
This is all in my setupTests.js file

NextJS: How to add screen loading for production build?

I want add screen loading in next js project. And I tried to do that with the Router component in next/router.
This is my _app.js in next.js project:
import {CookiesProvider} from 'react-cookie';
import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import withRedux from 'next-redux-wrapper'
import withReduxSaga from 'next-redux-saga'
import createStore from '../src/redux/store'
import Router from "next/router";
import {Loaded, Loading} from "../src/util/Utils";
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ctx})
}
return {pageProps}
}
render() {
Router.onRouteChangeStart = () => {
Loading()
};
Router.onRouteChangeComplete = () => {
Loaded()
};
Router.onRouteChangeError = () => {
Loaded()
};
const {Component, pageProps, store} = this.props;
return (
<CookiesProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</CookiesProvider>
)
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
This is Loaded() and Loading() functions:
export const Loaded = () => {
setTimeout(() => {
let loading = 'has-loading';
document.body.classList.remove(loading);
}, 100);
};
export const Loading = () => {
let loading = 'has-loading';
document.body.classList.add(loading);
};
The code works well when the project is under development mode. But when the project is built, the loading won't disappear.
Do you know solution of this issue or are you suggesting another solution?
Using apollo client and react hooks you could do as follow.
Example:
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { withApollo } from '../lib/apollo';
import UserCard from '../components/UserCard';
export const USER_INFO_QUERY = gql`
query getUser ($login: String!) {
user(login: $login) {
name
bio
avatarUrl
url
}
}
`;
const Index = () => {
const { query } = useRouter();
const { login = 'default' } = query;
const { loading, error, data } = useQuery(USER_INFO_QUERY, {
variables: { login },
});
if (loading) return 'Loading...'; // Loading component
if (error) return `Error! ${error.message}`; // Error component
const { user } = data;
return (
<UserCard
float
href={user.url}
headerImg="example.jpg"
avatarImg={user.avatarUrl}
name={user.name}
bio={user.bio}
/>
);
};
export default withApollo({ ssr: true })(Index);
More info here: https://github.com/zeit/next.js/tree/canary/examples/with-apollo
I added the following codes to a wrapper component and the problem was resolved.
componentDidMount() {
Loaded();
}
componentWillUnmount() {
Loading();
}

Resources