intlify / vue-i18n

Vue I18n for Vue 3
https://vue-i18n.intlify.dev/
MIT License
2.1k stars 323 forks source link

setting locale in jest test environment is not working correctly #1160

Open r-kober opened 2 years ago

r-kober commented 2 years ago

Reporting a bug?

in Jest with testing-library the changing of the language does not really work. Only the first call to change this.$i18n.locale changes the locale actually and it stays that way for the following tests in which the locale should be changed. If you try the same in the browser everything works fine.

Expected behavior

If you change $i18n.locale or i18n.global.locale it should change the locale in a jest environment everytime it is called.

Reproduction

https://github.com/r-kober/vue-i18n-next-jest-bug

System Info

System:
    OS: macOS 10.15.7
    CPU: (8) x64 Intel(R) Core(TM) i7-3615QM CPU @ 2.30GHz
    Memory: 135.39 MB / 8.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 18.7.0 - /usr/local/bin/node
    npm: 8.18.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 104.0.5112.101
    Safari: 15.6.1
  npmPackages:
    @vue/cli-plugin-babel: ~5.0.0 => 5.0.8 
    @vue/cli-plugin-eslint: ~5.0.0 => 5.0.8 
    @vue/cli-plugin-unit-jest: ~5.0.0 => 5.0.8 
    @vue/cli-service: ~5.0.0 => 5.0.8 
    @vue/test-utils: ^2.0.0-0 => 2.0.2 
    @vue/vue3-jest: ^27.0.0-alpha.1 => 27.0.0 
    vue: ^3.2.13 => 3.2.38 
    vue-i18n: ^9.2.2 => 9.2.2

Screenshot

No response

Additional context

The error came somewhere between Version 9.2.0-beta.34 and 9.2.0-beta.35, because in beta 34 it worked and also in 9.1.0.

Validations

kazupon commented 2 years ago

Thank you for your reporting!

This is weird behavior... I'll try to find out cause, and we will try to fix, if we will be able to find out.

ivolkoff commented 1 year ago

@r-kober You can try this.

import Form from './Form.vue';

export const i18n = createI18n({
  // set legacy to false
  legacy: false,
});

config.global.plugins = [i18n];

describe('Mounted Form', () => {
  test.each(['en', 'ru', 'uk'])('renders correctly %s', locale => {
    // set locale
    i18n.global.locale.value = locale;
    const wrapper = mount(Form);
    expect(wrapper.element).toMatchSnapshot();
  });
});
mdoesburg commented 1 year ago

@kazupon I can confirm that this is also an issue when using vitest.

It seems that doing this only works the first time:

i18n.global.locale.value = locale;
nagisaando commented 1 year ago

I am having the same issue with vitest + testing library. Here is the repo. https://github.com/nagisaando/vueI18n-test-problem

Let me know if I need to submit more info!

sronveaux commented 1 month ago

Hi @kazupon and first thanks for this fantastic package so many people use !

I encounter the exact same problem here with an app using Karma as a test runner with Webpack under the hood.
Karma runs the unit tests inside a real browser compared to Jest or Vitest and the problem is exactly the same ! Only the first change of locale is working...

I was wondering, as some time has passed since this issue was open, whether you had some time to investigate and/or have found what could be the cause of this problem...

Many thanks in advance for keeping us updated on this one.

ben-hamel commented 1 month ago

I believe the issue is due to Vue Testing Library(VTL). I've submitted an issue on their repo with a repo showing Vue utils working in similar tests - https://github.com/testing-library/vue-testing-library/issues/318

Global translations seem to work in VTL, but local scope definitely has an issue.

sronveaux commented 1 month ago

Hi @ben-hamel,

Unfortunaly, I also encounter the problem with VTU as I don't use VTL personally.

The way I implemented it on my side was to create an i18n instance with legacy: false and globalInjection: true then assign it to VTU config.global.plugin. That way it is available inside all tested components. (Note that I tried many variations with global translations, local translations, getting the reference with useI18n and always finish with the same problem...)
With this setup, I can change locale as much as I want BUT only in a single test. All the following tests will keep the locale fixed...

The only working way I've found is to recreate a new i18n instance before each test and associate it to global.plugins when mounting the component to test. However this would mean changing calls to mount or shallowMount in every file which is not very clever...

ben-hamel commented 1 month ago

Yeah, I'm unsure if there's difference due to mine being a Vitest setup. But you can check my repo and see translations are testing correctly with VTU. But with VTL I get the first test translating and then stops afterwards, but only for the local scoped translations.

morcth commented 1 week ago

I experienced the same with Vue Testing Library.

i18n.global.locale.value = language does not change the value correctly with a .each type test. Vitest.

If the each is the only test run and en in the first language tested, it works if there are 2 languages being tested.

If I run a test before the each test that also accesses locale, then the each will fail.

Was able to make it work with Vue Test Utils.

vue-i18n v9.1.10 works fine by the way. Not sure if that is the latest version that works though.

sronveaux commented 3 hours ago

Hi @kazupon,

Just some words to let you know I investigated a bit deeper in it and found what the problem is. Thanks to @r-kober information, it was quite easy to spot.

The problem happens since #977 was merged and since the effect scope is released when an I18n instance is disposed.
This is only a problem when a global instance is created and reused in multiple tests which is the case in the repos posted in this issue. If a new instance is created in every test, the problem doesn't happen.
I hope this information will be useful to other impacted people here.

Personally, I encountered it as I have a lot of components relying on Vue-I18n so I thought creating a plugin once and for all in the test suite entry point and assigning it to vue-test-utils config would be a good idea:

import { config } from '@vue/test-utils'
const i18nInstance = createI18n({ ... })
config.global.plugins = [i18nInstance]

But doing this, the plugin is re-installed and re-disposed in each unit test. That's why it works only once as the effect scope is destroyed after the first one !

I tried to add a boolean inside the I18nAdditionalOptions to test whether the effect scope should be stopped inside dispose and managed to make everything works as expected !

I'd be happy to open a PR with this if you want, however, I'd like to know what you think about it before writing it:

Just tell me how you feel about this ! Thanks in advance !