react-i18next icon indicating copy to clipboard operation
react-i18next copied to clipboard

language is not set during tests with vitest

Open phun-ky opened this issue 1 year ago • 6 comments

🐛 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

phun-ky avatar Feb 19 '24 13:02 phun-ky

make sure the file that contains the i18next.init call is included/required during your tests

adrai avatar Feb 19 '24 13:02 adrai

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

phun-ky avatar Feb 20 '24 07:02 phun-ky

hard to help, sorry

adrai avatar Feb 20 '24 07:02 adrai

@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
  },
},

geritol avatar May 21 '24 15:05 geritol

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

RomRom1 avatar Jun 24 '24 07:06 RomRom1