Repository: jhipster/jhipster-lite
Pull request #11015 (2024-09-29 16:15:16)

I18n modules in tests ci
Sounds good to include i18n modules in the test-ci

Doing that, I saw that test cases were missing and also fixed a bug on it.

Will be better for react once refactor will be done. ATM just ignoring i18n.ts in coverage


Patch:
diff --git a/src/main/java/tech/jhipster/lite/generator/client/react/i18n/domain/ReactI18nModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/client/react/i18n/domain/ReactI18nModuleFactory.java
index 5a43f31d1..7a7f520af 100644
--- a/src/main/java/tech/jhipster/lite/generator/client/react/i18n/domain/ReactI18nModuleFactory.java
+++ b/src/main/java/tech/jhipster/lite/generator/client/react/i18n/domain/ReactI18nModuleFactory.java
@@ -78,6 +78,9 @@ public class ReactI18nModuleFactory {
              });
            });""" )
         .and()
+        .in(path("./vitest.config.ts"))
+          .add(lineAfterText("'src/main/webapp/app/index.tsx',"), properties.indentation().times(4) + "'src/main/webapp/app/i18n.ts',")
+        .and()
       .and()
       .build();
     //@formatter:off
diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java
index 79990ebec..6f9ce3933 100644
--- a/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java
+++ b/src/main/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactory.java
@@ -49,6 +49,9 @@ public class VueI18nModuleFactory {
       .batch(TEST_SOURCE, to(INDEX_TEST + "webapp/unit/home/infrastructure/primary/"))
         .addFile("HomePageVue.spec.ts")
         .and()
+      .batch(APP_SOURCE, to(INDEX_TEST + "webapp/unit"))
+        .addFile("i18n.spec.ts")
+        .and()
       .and()
       .mandatoryReplacements()
         .in(path(INDEX + "main.ts"))
diff --git a/src/main/resources/generator/client/common/i18n/Translations.ts b/src/main/resources/generator/client/common/i18n/Translations.ts
index e82e63bb1..72d35ac7e 100644
--- a/src/main/resources/generator/client/common/i18n/Translations.ts
+++ b/src/main/resources/generator/client/common/i18n/Translations.ts
@@ -10,13 +10,13 @@ const toLanguage = ([key, value]: [string, ResourceKey]): [string, ResourceLangu
   },
 ];
 
-const mergeTranslations = (translations: Translations[]): Translations =>
+export const mergeTranslations = (translations: Translations[]): Translations =>
   translations
     .flatMap(translations => Object.entries(translations))
     .reduce(
       (acc, [key, translation]) => ({
         ...acc,
-        [key]: acc[key] ? { ...acc[key], ...translation } : translation,
+        [key]: acc[key] ? { ...acc[key], ...deepMerge(acc[key], translation) } : translation,
       }),
       {} as Translations,
     );
@@ -31,3 +31,12 @@ export const toTranslationResources = (...translations: Translations[]): Resourc
       }),
       {},
     );
+
+const deepMerge = (target: Translation, source: Translation): Translation => {
+  for (const key of Object.keys(source)) {
+    if (source[key] instanceof Object && key in target) {
+      Object.assign(source[key], deepMerge(target[key] as Translation, source[key] as Translation));
+    }
+  }
+  return { ...target, ...source };
+};
diff --git a/src/main/resources/generator/client/common/i18n/i18n.spec.ts b/src/main/resources/generator/client/common/i18n/i18n.spec.ts
new file mode 100644
index 000000000..326c93a96
--- /dev/null
+++ b/src/main/resources/generator/client/common/i18n/i18n.spec.ts
@@ -0,0 +1,45 @@
+import i18n from '@/i18n';
+import { mergeTranslations } from '@/Translations';
+
+describe('i18n configuration', () => {
+  it('loads en translation', () => {
+    expect(i18n.getResourceBundle('en', '')['home']['translationEnabled']).toEqual('Internationalization enabled');
+  });
+
+  it('loads fr translation', () => {
+    expect(i18n.getResourceBundle('fr', '')['home']['translationEnabled']).toEqual('Internationalisation activée');
+  });
+
+  describe('mergeTranslations function', () => {
+    it('merges translations correctly when keys overlap', () => {
+      const translationSet1 = {
+        en: {
+          home: {
+            translationEnabled: 'Internationalization enabled',
+            welcome: 'Welcome',
+          },
+        },
+      };
+
+      const translationSet2 = {
+        en: {
+          home: {
+            anotherMessage: 'Another message',
+          },
+        },
+      };
+
+      const mergedResult = mergeTranslations([translationSet1, translationSet2]);
+
+      expect(mergedResult).toEqual({
+        en: {
+          home: {
+            translationEnabled: 'Internationalization enabled',
+            welcome: 'Welcome',
+            anotherMessage: 'Another message',
+          },
+        },
+      });
+    });
+  });
+});
diff --git a/src/main/resources/generator/client/react/i18n/src/main/webapp/app/i18n.ts b/src/main/resources/generator/client/react/i18n/src/main/webapp/app/i18n.ts
index 2a914343d..7c8b49676 100644
--- a/src/main/resources/generator/client/react/i18n/src/main/webapp/app/i18n.ts
+++ b/src/main/resources/generator/client/react/i18n/src/main/webapp/app/i18n.ts
@@ -4,7 +4,7 @@ import { initReactI18next } from 'react-i18next';
 import LanguageDetector from 'i18next-browser-languagedetector';
 import Backend from 'i18next-http-backend';
 
-i18n
+void i18n
   .use(Backend)
   .use(LanguageDetector)
   .use(initReactI18next)


Test patch:
diff --git a/src/test/features/client/vue-i18n.feature b/src/test/features/client/vue-i18n.feature
index 55b34b632..3a6c24d46 100644
--- a/src/test/features/client/vue-i18n.feature
+++ b/src/test/features/client/vue-i18n.feature
@@ -17,3 +17,5 @@ Feature: Vue i18n
       | en.ts |
     And I should have files in "src/test"
       | setupTests.ts |
+    And I should have files in "src/test/webapp/unit"
+      | i18n.spec.ts |
diff --git a/src/test/java/tech/jhipster/lite/generator/client/react/i18n/domain/ReactI18nModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/react/i18n/domain/ReactI18nModuleFactoryTest.java
index a07c0a976..57348ee58 100644
--- a/src/test/java/tech/jhipster/lite/generator/client/react/i18n/domain/ReactI18nModuleFactoryTest.java
+++ b/src/test/java/tech/jhipster/lite/generator/client/react/i18n/domain/ReactI18nModuleFactoryTest.java
@@ -21,7 +21,7 @@ class ReactI18nModuleFactoryTest {
       JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).projectBaseName("jhipster").build()
     );
 
-    JHipsterModuleAsserter asserter = assertThatModuleWithFiles(module, packageJsonFile(), app(), appTest(), index());
+    JHipsterModuleAsserter asserter = assertThatModuleWithFiles(module, packageJsonFile(), app(), appTest(), index(), vitest());
 
     asserter
       .hasFile("package.json")
@@ -41,7 +41,10 @@ class ReactI18nModuleFactoryTest {
       .and()
       .hasPrefixedFiles("src/main/webapp/assets/locales/", "en/translation.json", "fr/translation.json")
       .hasFile("src/test/webapp/unit/home/infrastructure/primary/HomePage.spec.tsx")
-      .containing("describe('Home I18next', () => {");
+      .containing("describe('Home I18next', () => {")
+      .and()
+      .hasFile("vitest.config.ts")
+      .containing("'src/main/webapp/app/i18n.ts',");
   }
 
   private ModuleFile app() {
@@ -58,4 +61,8 @@ class ReactI18nModuleFactoryTest {
   private ModuleFile index() {
     return file("src/test/resources/projects/react-app/index.tsx", "src/main/webapp/app/index.tsx");
   }
+
+  private ModuleFile vitest() {
+    return file("src/test/resources/projects/react-app/vitest.config.ts.template", "./vitest.config.ts");
+  }
 }
diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java
index 2fd3ba5c3..774a3b4ba 100644
--- a/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java
+++ b/src/test/java/tech/jhipster/lite/generator/client/vue/i18n/domain/VueI18nModuleFactoryTest.java
@@ -35,6 +35,8 @@ class VueI18nModuleFactoryTest {
       .and()
       .hasFile("src/main/webapp/app/i18n.ts")
       .and()
+      .hasFile("src/test/webapp/unit/i18n.spec.ts")
+      .and()
       .hasFile("src/main/webapp/app/Translations.ts")
       .and()
       .hasFile("src/main/webapp/app/home/HomeTranslations.ts")
diff --git a/src/test/resources/projects/react-app/vitest.config.ts.template b/src/test/resources/projects/react-app/vitest.config.ts.template
new file mode 100644
index 000000000..b75e04ba3
--- /dev/null
+++ b/src/test/resources/projects/react-app/vitest.config.ts.template
@@ -0,0 +1,47 @@
+/// <reference types="vitest" />
+
+import tsconfigPaths from 'vite-tsconfig-paths';
+import { configDefaults, defineConfig } from 'vitest/config';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+  plugins: [react(), tsconfigPaths()],
+  test: {
+    reporters: ['verbose', 'vitest-sonar-reporter'],
+    outputFile: {
+      'vitest-sonar-reporter': 'target/test-results/TESTS-results-sonar.xml',
+    },
+    globals: true,
+    logHeapUsage: true,
+    poolOptions: {
+      threads: {
+        minThreads: 1,
+        maxThreads: 2,
+      },
+    },
+    environment: 'jsdom',
+    cache: false,
+    include: ['src/test/webapp/unit/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
+    coverage: {
+      thresholds: {
+        perFile: true,
+        autoUpdate: true,
+        100: true,
+      },
+      include: ['src/main/webapp/**/*.ts?(x)'],
+      exclude: [
+        ...configDefaults.coverage.exclude as string[],
+        'src/main/webapp/app/index.tsx',
+      ],
+      provider: 'istanbul',
+      reportsDirectory: 'target/test-results/',
+      reporter: ['html', 'json-summary', 'text', 'text-summary', 'lcov', 'clover'],
+      watermarks: {
+        statements: [100, 100],
+        branches: [100, 100],
+        functions: [100, 100],
+        lines: [100, 100],
+      },
+    },
+  },
+});
diff --git a/tests-ci/generate.sh b/tests-ci/generate.sh
index 94d8b732c..3c3a9683b 100755
--- a/tests-ci/generate.sh
+++ b/tests-ci/generate.sh
@@ -404,6 +404,7 @@ elif [[ $application == 'reactapp' ]]; then
     "prettier" \
     "typescript" \
     "react-core" \
+    "react-i18next" \
     "cypress-component-tests"
 
   cucumber_with_jwt
@@ -420,6 +421,7 @@ elif [[ $application == 'vueapp' ]]; then
     "typescript" \
     "prettier" \
     "vue-core" \
+    "vue-i18next" \
     "vue-pinia" \
     "vue-oauth2-keycloak" \
     "playwright-component-tests" \