How to test react-native components which are using react-i18next? - react-native

I'm trying to use jest to start testing an established RN app. The majority of the components are wrapped in withNamespaces provided by react-i18next.
When i run tests i have this error:
FAIL tests/setting.test.js
● Test suite failed to run
TypeError: Cannot read property 'type' of undefined
4 |
5 | i18n
> 6 | .use(initReactI18next)
| ^
7 | .init({
8 | fallbackLng: 'en',
9 | resources: {
at I18n.use (node_modules/i18next/dist/commonjs/i18next.js:260:16)
at Object.<anonymous> (config/i18nForTest.js:6:1)
at Object.<anonymous> (tests/setting.test.js:5:43)
I followed the doc example and what i've done basically is :
made i18n config for tests :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
i18n
.use(initReactI18next)
.init({
fallbackLng: 'en',
resources: {
en: {},
fr: {}
},
// have a common namespace used around the full app
ns: ['translations'],
defaultNS: 'translations',
debug: true,
interpolation: {
escapeValue: false, // not needed for react!!
},
});
export default i18n;
then wrote some tests:
import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';
import {I18nextProvider} from 'react-i18next';
import i18n from 'config/i18nForTest';
import Settings from 'containers/Settings';
it('Does Settings renders correctly?', () => {
const tree = renderer
.create(
<I18nextProvider i18n={i18n}>
<Settings t= {key => key} />
</I18nextProvider>,
)
.toJSON();
expect(tree).toMatchSnapshot();
});
you can find here my jest config in package.json:
"jest": {
"setupFiles": ["<rootDir>/jest.setup.js"],
"preset": "react-native",
"testMatch": [
"<rootDir>/tests/**/*.test.js?(x)",
"<rootDir>/src/**/*.test.js"
],
"transformIgnorePatterns": ["node_modules/(?!(#react-native-community|react-navigation|react-native))"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
}
},
THANKS!

I finally found a solution for my problem.
I think the testing example provided in the doc is for v10 and i'm using v9.
So i have modified the i18n config test to use reactI18nextModule so its like this now:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { reactI18nextModule } from 'react-i18next';
i18n
.use(reactI18nextModule)
.init({
fallbackLng: 'en',
resources: {
en: {},
fr: {}
},
// have a common namespace used around the full app
ns: ['translations'],
defaultNS: 'translations',
debug: true,
interpolation: {
escapeValue: false, // not needed for react!!
},
});
export default i18n;
after that now i can omit i18nProvider in the testing components like this:
import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';
import {I18nextProvider} from 'react-i18next';
import i18n from 'config/i18nForTest';
import {Settings} from 'containers/Settings'; // to get redux connected component import Setting without {}
it('Does Settings renders correctly?', () => {
const tree = renderer
.create(
<Settings t={key => key} />
)
.toJSON();
expect(tree).toMatchSnapshot();
});

Related

How to use Environment Variables inside Vue3+Vite component library?

I have created a component as part of my component library that I am building with Vue3 and Vite. Everything works well, except when I try to use environment variables. I want the app that consumes this component library to be able to provide the component with environment specific data.
I have played around and found that if I have a .env file as part of the component library project, I am able to access those variables, but I want to be able to provide that during runtime and not during build time.
Here is my vite.config.ts
import { defineConfig } from "vite";
import { resolve } from "path";
import vue from "#vitejs/plugin-vue";
import dts from "vite-plugin-dts";
export default ({ mode }) => {
return defineConfig({
optimizeDeps: {
exclude: ["vue-demi"],
},
plugins: [
vue(),
dts({
insertTypesEntry: true,
}),
],
server: {
open: true,
},
build: {
lib: {
entry: resolve(__dirname, "src/lib.ts"),
name: "complib",
fileName: "complib",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
exports: "named",
},
},
},
});
};
The entry looks like:
import { App, install } from "vue-demi";
import TestComp from "./components/TestComp.vue";
import "./tailwind.css";
install();
export default {
install: (app: App) => {
app.component("TestComp", TestComp);
},
};
export { Header };
And here is a minimal component TestComp.vue:
<script setup lang="ts">
import { onMounted } from "vue";
onMounted(() => {
console.log(import.meta.env.VITE_TEST_VAR);
});
</script>
<template>
<span>Test Comp</span>
</template>

Vite: vue-i18n build fail

Everything work fine in dev mode, but when try to npm run build, Unexpected token error in vue-i18n
import { createI18n } from 'vue-i18n';
import messages from '#intlify/vite-plugin-vue-i18n/messages'
const i18n = createI18n({
locale: 'en',
legacy: false,
fallbackLocale: 'en',
globalInjection: true,
messages
})
export default i18n;
//vite.config.js
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
import path from 'path'
import vueI18n from '#intlify/vite-plugin-vue-i18n'
export default defineConfig({
plugins: [
vue(),
vueI18n({
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
// compositionOnly: false,
// you need to set i18n resource including paths !
include: path.resolve(__dirname, './src/locales/**'),
}),
],
resolve: {
alias: {
'#': path.resolve(__dirname, './src'),
},
},
})
Error:
[commonjs--resolver] Unexpected token (249:14) in E:/Work/Vue/vue-sample/node_modules/vue-i18n/dist/vue-i18n.runtime.esm-bundler.js
247: if (locales.length) {
248: locales.forEach(locale => {
249: global.mergeLocaleMessage(locale, messages[locale]);
^
250: });
251: }

i18next react load namespaces only when needed

I have the following code that loads i18n translations. The l.json file exists only in a few pages but it is loaded always (based on the http requests that I see from the browser). How can I prevent it so that these are loaded only when needed?
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import ChainedBackend from 'i18next-chained-backend';
import HttpBackend from 'i18next-http-backend';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.use(ChainedBackend)
.init({
debug: true,
fallbackLng: "en-US",
supportedLngs: ['en-US', 'en-GB'],
ns: ['l', 't'],
defaultNS: 't',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
backend: {
backends: [
HttpBackend,
],
backendOptions: [
{
loadPath: 'https://example.com/{{lng}}/{{ns}}.json'
}
]
}
});

Vue-test-utils loading dependencies - Vue3

I'm developing some tests for single file components in VueJs. These components use font-awesome.
This is my App, as you can see I have fontawesome available for all child components.
import { createApp } from 'vue';
import App from './App.vue';
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap";
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome';
import { fas } from "#fortawesome/free-solid-svg-icons";
import { library } from '#fortawesome/fontawesome-svg-core';
library.add(fas);
createApp(App)
.component("font-awesome-icon", FontAwesomeIcon)
.mount('#app');
Here's a test
import { mount } from '#vue/test-utils'
import ListComponent from '#/components/ListComponent.vue'
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome';
let someData = [{
name: 'Some person name',
key: '2222222',
moreInfo: [
{title: 'aa'},
{title: 'bb'},
]
},
{
name: 'Some other person name',
key: '12321123,
moreInfo: [
{title: 'cc'},
{title: 'x'},
]
},
}];
let wrapper = mount(ListComponent, {
propsData: {
someData
},
stubs: {
'font-awesome-icon': FontAwesomeIcon
}
});
describe('ListadoImputados.vue', () => {.... tests ....});
Test details are not important, I don't know how to add / include font-awesome-icon in the context so i can avoid getting the following warnings
console.warn node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:40
[Vue warn]: Failed to resolve component: font-awesome-icon
I tried adding this dependency as a mock and stub but no luck. Also importing Fontawesome at the top of the file does not work, the warning is still showing. I was thinking maybe in creating a vue app in the test file and inject the component like this
createApp(App)
.component("font-awesome-icon", FontAwesomeIcon)
.mount('#app');
but I'm copying and pasting code and I'm not sure this is the right way.
Is there a way to add this dependencies to my test context?
I'm using Vue 3, vue-test-utils + jest
In Vue Test Utils v2 (for Vue 3), the stubs mounting option is moved into global.stubs. Also note that a stub does nothing by definition, so stubbing the component only requires providing the component name.
Your mounting options should look like this:
const wrapper = mount(ListComponent, {
global: {
stubs: ['FontAwesomeIcon']
}
})
If for some reason you need the actual component, you could technically provide the component definition as a "stub", but you'd also need to initialize the icons for it as you would in the app's startup:
// assume only `faUserSecret` icon used in `ListComponent`
import { library } from '#fortawesome/fontawesome-svg-core'
import { faUserSecret } from '#fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome'
library.add(faUserSecret)
//...
const wrapper = mount(ListComponent, {
global: {
stubs: { FontAwesomeIcon }
}
})

How to create a Quasar Framework boot file for vueI18n#next with the Vue Composition API?

We;re trying to install the new vueI18n#next package with Quasar Framework for use with the Vue 2 and the Vue Composition API. The Vue I18n docs mention this:
import { createApp } from 'vue'
import { createI18n, useI18n } from 'vue-i18n'
// call with I18n option
const i18n = createI18n({
legacy: false,
locale: 'ja',
messages: { en: { ... } }
})
const App = {
setup() {
// ...
const { t } = useI18n({ ... })
return { ... , t }
}
}
const app = createApp(App)
app.use(i18n)
app.mount('#app')
When we're trying to translate that to a Quasar Framework boot file we get an error on the app.setup part:
import { boot } from 'quasar/wrappers'
import messages from 'src/i18n'
import Vue from 'vue'
import { createI18n, useI18n, VueI18n } from 'vue-i18n'
declare module 'vue/types/vue' {
interface Vue {
i18n: VueI18n
}
}
const i18n = createI18n({
legacy: false,
locale: 'en-us',
fallbackLocale: 'en-us',
messages,
})
Vue.use(i18n)
export default boot(({ app }) => {
// Set i18n instance on app
app.setup = () => {
const { t } = useI18n()
return { t }
}
})
Error:
What is the correct way to install this?
Quasar is still on Vue 2, not Vue 3. So we're now using the vue-i18n-composable package instead with this boot file:
import { boot } from 'quasar/wrappers'
import { createI18n } from 'vue-i18n-composable'
import messages from 'src/i18n'
import VueI18n from 'vue-i18n'
declare module 'vue/types/vue' {
interface Vue {
i18n: VueI18n
}
}
const i18n = createI18n({
locale: 'en-us',
fallbackLocale: 'en-us',
messages,
})
export default boot(({ app }) => {
app.i18n = i18n
})