language is not set during tests with vitest
🐛 Bug Report
[!NOTE]
I do not think this is a bug, more of a configuration issue/documentation issue, because clearly, I am doing something wrong
Language is not set during tests with vitest. In a dependency I import, this code exists:
function useI18N() {
var _useTranslation = reactI18next.useTranslation(),
t = _useTranslation.t,
language = _useTranslation.i18n.language;
if (language !== 'nb' && language !== 'en') {
throw Error('Language must be either nb or en.');
}
return t;
}
function withI18n(Component) {
return function WrappedComponent(props) {
var t = useI18N();
return /*#__PURE__*/React__default["default"].createElement(Component, _extends({}, props, {
i18n: t
}));
};
}
This code throws throw Error('Language must be either nb or en.'); when testing using vitest. It does not throw for building with vite nor with the previous test runner jest (with practically same config).
That code is wrapping a component that is consumed by several components up until the file I am testing, which is using this:
export const renderWithI18NContext = (
component: ReactNode,
organisation?: Organisation,
locale = 'nb'
) => {
return render(componentWithI18NContext(component, organisation, locale));
};
const componentWithI18NContext = (
…
) => {
store.dispatch(…);
i18n.changeLanguage(locale);
return (
<Provider store={store}>
{/* @ts-ignore */}
<MemoryRouter>
<SWRConfig value={{ dedupingInterval: 0 }}>
<Some.Provider value={something}>
{component}
</Some.Provider>
</SWRConfig>
</MemoryRouter>
</Provider>
);
};
With an import i18n that looks like this:
use(initReactI18next).init({
lng: 'nb',
fallbackLng: 'nb',
resources: {
nb: {
translation: {
…
}
},
en: {
translation: {
…
}
}
},
interpolation: {
escapeValue: false // not needed for react as it escapes by default
}
});
export default i18n;
Which is consumed like this:
it('should pass', async () => {
renderWithI18NContext(
<ComponentTestContainer>
…
component={<ComponentToTest {...props} />}
/>
);
expect(…);
});
I've narrowed it down to react-i18next is not picking up language, i.e., that use(initReactI18next).init({…}) is not called, or something..
To Reproduce
I cannot produce a reproduction case due to the complexity of the internal (non public) dependencies, which I also think has something to do with this. As stated, I think this is a misconfiguration on my part, not a bug itself.
Expected behavior
That language is set.
Your Environment
- runtime version: node 20
- i18next version: "react-i18next": "14.0.5", "i18next": "23.7.16"
- os: Linux
- any other relevant information
make sure the file that contains the i18next.init call is included/required during your tests
@adrai thanks for the suggestion. I've tried that with no avail sadly :/ Tried to include it in the test, in the test setup file, the renderWithI18NContext wrapper and in the component I am testing.
hard to help, sorry
@phun-ky this might be due to something called "dual package hazard" https://github.com/vitest-dev/vitest/issues/3287#issuecomment-1534159966
Had a similar issue (main code was using different build of the package than the library
adding the following to vite.config.js solved the issue for me:
resolve: {
alias: {
'react-i18next': path.resolve(__dirname, './node_modules/react-i18next/dist/commonjs/index.js'), // https://github.com/vitest-dev/vitest/issues/3287#issuecomment-1534159966
},
},
Hi @phun-ky ,
I had the same problem and I solved it by using i18next-fs-backend in my component wrapper.
Also, to make it works, i18n instance needs to be created and initialized synchronously.
Please note the await on init call and the option initImmediate: false.
import {render as rtlRender} from '@testing-library/react'
import {I18nextProvider, initReactI18next} from "react-i18next";
import {createInstance} from "i18next";
import Backend from "i18next-fs-backend";
import translation from "../public/locales/en/translation.json"
const instance = createInstance()
await instance
.use(Backend)
.use(initReactI18next)
.init({
fallbackLng: "en",
debug: false,
interpolation: {
escapeValue: false
},
defaultNS: "translation",
initImmediate: false,
resources: {
en: {
translation
}
}
});
export const render = (ui: JSX.Element) => {
function Wrapper({children}: any): any {
return (
<I18nextProvider i18n={instance}>
{children}
</I18nextProvider>
)
}
rtlRender(ui, {wrapper: Wrapper})
}
Hope that can help