Repository: jhipster/jhipster-lite
Pull request #10914 (2024-09-19 21:39:44)

feat: react module now depends on typescript module
See #10421


Patch:
diff --git a/documentation/hexagonal-architecture.md b/documentation/hexagonal-architecture.md
index 7853d0708..031aa3166 100644
--- a/documentation/hexagonal-architecture.md
+++ b/documentation/hexagonal-architecture.md
@@ -23,7 +23,7 @@ Here are some of the properties we are looking for when using this kind of archi
 
 "The Only Constant in Life Is Change."- Heraclitus. This can't be more true in software (hence [the name](https://en.wikipedia.org/wiki/Software)) and, if you have just a few weeks of experience, you know that "the users don't know what they want!!!". If the first quote is true the second one is fallacious!
 
-It's not that the users don't know what they want, in fact, nobody knows. Building software is not done with somebody telling the others what to do, it's done with [productive partnerships](https://manifesto.softwarecraftsmanship.org/). Yes, that means people will change their mind times and times again (and this is totally fine!).
+It's not that the users don't know what they want, in fact, nobody knows. Building a software is not done with somebody telling the others what to do, it's done with [productive partnerships](https://manifesto.softwarecraftsmanship.org/). Yes, that means people will change their mind times and times again (and this is totally fine!).
 
 So, as professional software developers we have to ensure that the code we are writing can welcome those changes. This often starts by lowering the solution complexity!
 
@@ -35,7 +35,7 @@ We often talk about 3 types of complexity:
 
 The hexagonal architecture allows us to reduce all those complexities to their bare minimum by giving clear responsibilities to each part of our software.
 
-The very clear separation of concern enforced by the architecture eases automatic testing of each part since each part is only doing one thing. Being able to build solid tests is also a great way to build a change welcoming software!
+The very clear separation of concern enforced by the architecture eases automatic testing of each parts since each part is only doing one thing. Being able to build solid tests is also a great way to build a change welcoming software!
 
 > Even if the architecture eases tests writing, being able to write good tests takes time and practice!
 
diff --git a/pom.xml b/pom.xml
index d059265a6..49f6b6e74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,7 +69,7 @@
     <springdoc-openapi-starter-webmvc-ui.version>2.6.0</springdoc-openapi-starter-webmvc-ui.version>
     <properties-maven-plugin.version>1.2.1</properties-maven-plugin.version>
     <sonar-maven-plugin.version>4.0.0.4121</sonar-maven-plugin.version>
-    <apache-commons-io.version>2.17.0</apache-commons-io.version>
+    <apache-commons-io.version>2.16.1</apache-commons-io.version>
     <cucumber.version>7.18.1</cucumber.version>
     <maven-model-helper.version>37</maven-model-helper.version>
     <mustache.java.version>0.9.14</mustache.java.version>
@@ -82,7 +82,7 @@
     <frontend-maven-plugin.version>1.15.0</frontend-maven-plugin.version>
     <checksum-maven-plugin.version>1.11</checksum-maven-plugin.version>
     <maven-antrun-plugin.version>3.1.0</maven-antrun-plugin.version>
-    <approvaltests.version>24.5.0</approvaltests.version>
+    <approvaltests.version>24.4.0</approvaltests.version>
   </properties>
 
   <dependencyManagement>
diff --git a/src/main/java/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactory.java
index 6971f08f7..ce433aaeb 100644
--- a/src/main/java/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactory.java
+++ b/src/main/java/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactory.java
@@ -3,12 +3,14 @@ package tech.jhipster.lite.generator.client.react.core.domain;
 import static tech.jhipster.lite.module.domain.JHipsterModule.*;
 import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.COMMON;
 import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.REACT;
-import static tech.jhipster.lite.module.domain.packagejson.NodeModuleFormat.MODULE;
 
+import java.util.function.Consumer;
+import tech.jhipster.lite.module.domain.Indentation;
 import tech.jhipster.lite.module.domain.JHipsterModule;
 import tech.jhipster.lite.module.domain.file.JHipsterDestination;
 import tech.jhipster.lite.module.domain.file.JHipsterSource;
 import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
+import tech.jhipster.lite.module.domain.replacement.MandatoryReplacer;
 
 public class ReactCoreModulesFactory {
 
@@ -27,36 +29,27 @@ public class ReactCoreModulesFactory {
   private static final JHipsterDestination PRIMARY_APP_DESTINATION = APP_DESTINATION.append(PRIMARY_APP);
 
   private static final String TEST_PRIMARY = "src/test/webapp/unit/common/primary/app";
+  private static final String DEFAULT_TSCONFIG_PATH = "\"@/*\": [\"src/main/webapp/app/*\"]";
 
   public JHipsterModule buildModule(JHipsterModuleProperties properties) {
     //@formatter:off
     return moduleBuilder(properties)
       .preCommitActions(stagedFilesFilter("{src/**/,}*.{ts,tsx}"), preCommitCommands("eslint --fix", "prettier --write"))
       .packageJson()
-        .type(MODULE)
+        .removeDevDependency(packageName("@tsconfig/recommended"))
         .addDevDependency(packageName("@testing-library/dom"), REACT)
         .addDevDependency(packageName("@testing-library/react"), REACT)
         .addDevDependency(packageName("@types/node"), COMMON)
         .addDevDependency(packageName("@types/react"), REACT)
         .addDevDependency(packageName("@types/react-dom"), REACT)
         .addDevDependency(packageName("@tsconfig/vite-react"), REACT)
-        .addDevDependency(packageName("@typescript-eslint/eslint-plugin"), COMMON)
         .addDevDependency(packageName("@vitejs/plugin-react"), REACT)
-        .addDevDependency(packageName("@vitest/coverage-istanbul"), COMMON)
-        .addDevDependency(packageName("typescript-eslint"), COMMON)
-        .addDevDependency(packageName("globals"), COMMON)
-        .addDevDependency(packageName("eslint"), COMMON)
         .addDevDependency(packageName("eslint-plugin-react"), REACT)
         .addDevDependency(packageName("jsdom"), COMMON)
-        .addDevDependency(packageName("typescript"), COMMON)
         .addDevDependency(packageName("ts-node"), REACT)
         .addDevDependency(packageName("vite"), COMMON)
-        .addDevDependency(packageName("vite-tsconfig-paths"), COMMON)
-        .addDevDependency(packageName("vitest"), COMMON)
-        .addDevDependency(packageName("vitest-sonar-reporter"), COMMON)
         .addDependency(packageName("react"), REACT)
         .addDependency(packageName("react-dom"), REACT)
-        .addDevDependency(packageName("npm-run-all2"), COMMON)
         .addScript(scriptKey("dev"), scriptCommand("npm-run-all dev:*"))
         .addScript(scriptKey("dev:vite"), scriptCommand("vite"))
         .addScript(scriptKey("build"), scriptCommand("npm-run-all build:*"))
@@ -64,19 +57,10 @@ public class ReactCoreModulesFactory {
         .addScript(scriptKey("build:vite"), scriptCommand("vite build --emptyOutDir"))
         .addScript(scriptKey("preview"), scriptCommand("vite preview"))
         .addScript(scriptKey("start"), scriptCommand("vite"))
-        .addScript(scriptKey("lint"), scriptCommand("eslint ."))
-        .addScript(scriptKey("watch"), scriptCommand("npm-run-all --parallel watch:*"))
-        .addScript(scriptKey("watch:tsc"), scriptCommand("tsc --noEmit --watch"))
-        .addScript(scriptKey("test"), scriptCommand("npm run watch:test"))
-        .addScript(scriptKey("test:coverage"), scriptCommand("vitest run --coverage"))
-        .addScript(scriptKey("watch:test"), scriptCommand("vitest --"))
       .and()
       .files()
         .batch(SOURCE, to("."))
-          .addFile("tsconfig.json")
           .addTemplate("vite.config.ts")
-          .addTemplate("vitest.config.ts")
-          .addTemplate("eslint.config.js")
           .and()
         .batch(APP_SOURCE, APP_DESTINATION)
           .addTemplate("index.css")
@@ -92,7 +76,93 @@ public class ReactCoreModulesFactory {
           .addFile("ReactLogo.png")
           .and()
         .and()
+      .apply(patchEslintConfig(properties))
+      .apply(patchTsConfig(properties))
+      .apply(patchVitestConfig(properties))
       .build();
     //@formatter:on
   }
+
+  private Consumer<JHipsterModuleBuilder> patchEslintConfig(JHipsterModuleProperties properties) {
+    String reactConfig =
+      """
+      \t\tfiles: ['src/main/webapp/**/*.{ts,tsx}', 'src/main/webapp/**/*.spec.ts'],
+      \t\textends: [...typescript.configs.recommendedTypeChecked, react],
+      \t\tsettings: {
+      \t\t\treact: {
+      \t\t\t\tversion: 'detect',
+      \t\t\t},
+      \t\t},\
+      """.replace("\t", properties.indentation().spaces());
+    //@formatter:off
+    return moduleBuilder -> moduleBuilder
+      .mandatoryReplacements()
+        .in(path("eslint.config.js"))
+          .add(lineAfterRegex("from 'typescript-eslint'"), "import react from 'eslint-plugin-react/configs/recommended.js';")
+            .add(regex("\\s+\\.\\.\\.typescript\\.configs\\.recommended.*"), "")
+            .add(regex("[ \\t]+files: \\['src/\\*/webapp/\\*\\*\\/\\*\\.ts'],"), reactConfig)
+            .add(
+              lineAfterRegex("globals: \\{ \\.\\.\\.globals\\.browser },"),
+              """
+              \t\t\tparserOptions: {
+              \t\t\t\tproject: ['./tsconfig.json'],
+              \t\t\t},\
+              """.replace("\t", properties.indentation().spaces())
+            )
+            .add(
+              regex("[ \\t]+quotes: \\['error', 'single', \\{ avoidEscape: true }],"),
+              """
+              \t\t\t'react/react-in-jsx-scope': 'off',
+              \t\t\t'@typescript-eslint/no-misused-promises': [
+              \t\t\t\t'error',
+              \t\t\t\t{
+              \t\t\t\t\tchecksVoidReturn: false,
+              \t\t\t\t}
+              \t\t\t],\
+              """.replace("\t", properties.indentation().spaces())
+            )
+          .and()
+          .and();
+    //@formatter:on
+  }
+
+  private Consumer<JHipsterModuleBuilder> patchTsConfig(JHipsterModuleProperties properties) {
+    String pathsReplacement =
+      DEFAULT_TSCONFIG_PATH + "," + LINE_BREAK + properties.indentation().times(3) + "\"@assets/*\": [\"src/main/webapp/assets/*\"]";
+    //@formatter:off
+    return moduleBuilder -> moduleBuilder
+      .mandatoryReplacements()
+        .in(path("tsconfig.json"))
+          .add(text("@tsconfig/recommended/tsconfig.json"), "@tsconfig/vite-react/tsconfig.json")
+          .add(tsConfigCompilerOption("composite", false, properties.indentation()))
+          .add(tsConfigCompilerOption("forceConsistentCasingInFileNames", true, properties.indentation()))
+          .add(tsConfigCompilerOption("allowSyntheticDefaultImports", true, properties.indentation()))
+          .add(text(DEFAULT_TSCONFIG_PATH), pathsReplacement)
+          .and()
+      .and();
+    //@formatter:on
+  }
+
+  private static MandatoryReplacer tsConfigCompilerOption(String optionName, boolean optionValue, Indentation indentation) {
+    String compilerOption = indentation.times(2) + "\"%s\": %s,".formatted(optionName, optionValue);
+    return new MandatoryReplacer(lineAfterRegex("\"compilerOptions\":"), compilerOption);
+  }
+
+  private Consumer<JHipsterModuleBuilder> patchVitestConfig(JHipsterModuleProperties properties) {
+    //@formatter:off
+    return moduleBuilder -> moduleBuilder
+      .mandatoryReplacements()
+        .in(path("vitest.config.ts"))
+          .add(lineAfterRegex("from 'vitest/config';"), "import react from '@vitejs/plugin-react';")
+          .add(text( "plugins: ["), "plugins: [react(), ")
+          .add(text("environment: 'node',"), "environment: 'jsdom',")
+          .add(vitestCoverageExclusion(properties,"src/main/webapp/app/index.tsx"))
+          .and();
+    //@formatter:on
+  }
+
+  private static MandatoryReplacer vitestCoverageExclusion(JHipsterModuleProperties properties, String filePattern) {
+    Indentation indentation = properties.indentation();
+    return new MandatoryReplacer(lineAfterRegex("configDefaults.coverage.exclude"), indentation.times(4) + "'" + filePattern + "',");
+  }
 }
diff --git a/src/main/java/tech/jhipster/lite/generator/client/react/core/infrastructure/primary/ReactCoreModulesConfiguration.java b/src/main/java/tech/jhipster/lite/generator/client/react/core/infrastructure/primary/ReactCoreModulesConfiguration.java
index e9c49651f..a62c0e7fd 100644
--- a/src/main/java/tech/jhipster/lite/generator/client/react/core/infrastructure/primary/ReactCoreModulesConfiguration.java
+++ b/src/main/java/tech/jhipster/lite/generator/client/react/core/infrastructure/primary/ReactCoreModulesConfiguration.java
@@ -1,14 +1,12 @@
 package tech.jhipster.lite.generator.client.react.core.infrastructure.primary;
 
-import static tech.jhipster.lite.generator.slug.domain.JHLiteFeatureSlug.*;
+import static tech.jhipster.lite.generator.slug.domain.JHLiteFeatureSlug.CLIENT_CORE;
 import static tech.jhipster.lite.generator.slug.domain.JHLiteModuleSlug.*;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import tech.jhipster.lite.generator.client.react.core.application.ReactCoreApplicationService;
-import tech.jhipster.lite.module.domain.resource.JHipsterModuleOrganization;
-import tech.jhipster.lite.module.domain.resource.JHipsterModulePropertiesDefinition;
-import tech.jhipster.lite.module.domain.resource.JHipsterModuleResource;
+import tech.jhipster.lite.module.domain.resource.*;
 
 @Configuration
 class ReactCoreModulesConfiguration {
@@ -21,7 +19,7 @@ class ReactCoreModulesConfiguration {
       .slug(REACT_CORE)
       .propertiesDefinition(properties())
       .apiDoc("Frontend - React", "Add React+Vite with minimal CSS")
-      .organization(JHipsterModuleOrganization.builder().feature(CLIENT_CORE).addDependency(INIT).addDependency(PRETTIER).build())
+      .organization(JHipsterModuleOrganization.builder().feature(CLIENT_CORE).addDependency(TYPESCRIPT).addDependency(PRETTIER).build())
       .tags("client", REACT)
       .factory(react::buildModule);
   }
diff --git a/src/main/resources/generator/dependencies/pom.xml b/src/main/resources/generator/dependencies/pom.xml
index a383ae80c..a8ac30e5d 100644
--- a/src/main/resources/generator/dependencies/pom.xml
+++ b/src/main/resources/generator/dependencies/pom.xml
@@ -34,7 +34,7 @@
     <webjars-locator.version>0.52</webjars-locator.version>
     <htmx-webjars.version>2.0.2</htmx-webjars.version>
     <alpinejs-webjars.version>3.14.1</alpinejs-webjars.version>
-    <protobuf.version>4.28.2</protobuf.version>
+    <protobuf.version>4.28.1</protobuf.version>
     <proto-backwards-compatibility.version>1.0.7</proto-backwards-compatibility.version>
     <jmolecules-bom.version>2023.1.4</jmolecules-bom.version>
     <jqassistant.version>2.4.0</jqassistant.version>
diff --git a/src/main/resources/generator/dependencies/react/package.json b/src/main/resources/generator/dependencies/react/package.json
index cfbfc60e9..5a28a1ee1 100644
--- a/src/main/resources/generator/dependencies/react/package.json
+++ b/src/main/resources/generator/dependencies/react/package.json
@@ -5,7 +5,7 @@
   "license": "Apache-2.0",
   "type": "module",
   "dependencies": {
-    "@nextui-org/react": "2.4.8",
+    "@nextui-org/react": "2.4.7",
     "axios": "1.7.7",
     "react": "18.3.1",
     "react-dom": "18.3.1",
diff --git a/src/main/webapp/app/module/primary/modules-patch/ModulesPatch.component.ts b/src/main/webapp/app/module/primary/modules-patch/ModulesPatch.component.ts
index 4d11d0464..90bd17fdb 100644
--- a/src/main/webapp/app/module/primary/modules-patch/ModulesPatch.component.ts
+++ b/src/main/webapp/app/module/primary/modules-patch/ModulesPatch.component.ts
@@ -218,7 +218,7 @@ export default defineComponent({
       return selectedTag.value === tag;
     };
 
-    const toggleTag = (tag: string): void => {
+    const toogleTag = (tag: string): void => {
       if (selectedTag.value === tag) {
         selectedTag.value = undefined;
       } else {
@@ -295,7 +295,7 @@ export default defineComponent({
       displayedModulesCount,
       totalModulesCount,
       isTagSelected,
-      toggleTag,
+      toogleTag,
       updateSearch,
       applyModule,
       projectFolderUpdated,
diff --git a/src/main/webapp/app/module/primary/modules-patch/ModulesPatch.html b/src/main/webapp/app/module/primary/modules-patch/ModulesPatch.html
index b4e37804d..c02dab257 100644
--- a/src/main/webapp/app/module/primary/modules-patch/ModulesPatch.html
+++ b/src/main/webapp/app/module/primary/modules-patch/ModulesPatch.html
@@ -3,9 +3,9 @@
   <div class="jhipster-modules-list jhlite-menu-content-template--content">
     <div class="jhipster-modules-filters">
       <div class="jhipster-modules-filters--tags">
-        <TagFilterVue tag="init" :isSelected="isTagSelected('init')" @selected="toggleTag('init')" />
-        <TagFilterVue tag="server" :isSelected="isTagSelected('server')" @selected="toggleTag('server')" />
-        <TagFilterVue tag="client" :isSelected="isTagSelected('client')" @selected="toggleTag('client')" />
+        <TagFilterVue tag="init" :isSelected="isTagSelected('init')" @selected="toogleTag('init')" />
+        <TagFilterVue tag="server" :isSelected="isTagSelected('server')" @selected="toogleTag('server')" />
+        <TagFilterVue tag="client" :isSelected="isTagSelected('client')" @selected="toogleTag('client')" />
       </div>
 
       <input
diff --git a/src/main/resources/generator/client/react/core/eslint.config.js.mustache b/src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.eslint.config.js.approved.txt
similarity index 79%
rename from src/main/resources/generator/client/react/core/eslint.config.js.mustache
rename to src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.eslint.config.js.approved.txt
index 5e0322fd4..e8134c13e 100644
--- a/src/main/resources/generator/client/react/core/eslint.config.js.mustache
+++ b/src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.eslint.config.js.approved.txt
@@ -1,6 +1,6 @@
+import js from '@eslint/js';
 import globals from 'globals';
 import typescript from 'typescript-eslint';
-import eslint from '@eslint/js';
 import react from 'eslint-plugin-react/configs/recommended.js';
 
 export default typescript.config(
@@ -12,9 +12,9 @@ export default typescript.config(
     },
   },
   {
-    ignores: ['{{projectBuildDirectory}}/'],
+    ignores: ['target/'],
   },
-  eslint.configs.recommended,
+  js.configs.recommended,
   {
     files: ['src/main/webapp/**/*.{ts,tsx}', 'src/main/webapp/**/*.spec.ts'],
     extends: [...typescript.configs.recommendedTypeChecked, react],
@@ -24,9 +24,7 @@ export default typescript.config(
       },
     },
     languageOptions: {
-      globals: {
-        ...globals.browser,
-      },
+      globals: { ...globals.browser },
       parserOptions: {
         project: ['./tsconfig.json'],
       },
@@ -36,7 +34,7 @@ export default typescript.config(
       '@typescript-eslint/no-misused-promises': [
         'error',
         {
-          'checksVoidReturn': false
+          checksVoidReturn: false,
         }
       ],
     },
diff --git a/src/main/resources/generator/client/react/core/tsconfig.json b/src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.tsconfig.json.approved.txt
similarity index 100%
rename from src/main/resources/generator/client/react/core/tsconfig.json
rename to src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.tsconfig.json.approved.txt
index bbaf02392..480eede51 100644
--- a/src/main/resources/generator/client/react/core/tsconfig.json
+++ b/src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.tsconfig.json.approved.txt
@@ -4,8 +4,8 @@
     "allowSyntheticDefaultImports": true,
     "forceConsistentCasingInFileNames": true,
     "composite": false,
-    "baseUrl": ".",
     "types": ["vitest/globals"],
+    "baseUrl": ".",
     "paths": {
       "@/*": ["src/main/webapp/app/*"],
       "@assets/*": ["src/main/webapp/assets/*"]


Test patch:
diff --git a/src/test/features/client/react-i18n.feature b/src/test/features/client/react-i18n.feature
index 38eaf1d4b..d97724c17 100644
--- a/src/test/features/client/react-i18n.feature
+++ b/src/test/features/client/react-i18n.feature
@@ -3,6 +3,7 @@ Feature: React i18n
   Scenario: Should apply react i18n module to react
     When I apply modules to default project
       | init          |
+      | typescript    |
       | react-core    |
       | react-i18next |
     Then I should have files in "src/main/webapp/app"
diff --git a/src/test/features/client/react-jwt.feature b/src/test/features/client/react-jwt.feature
index 898a0eef3..566a15b6f 100644
--- a/src/test/features/client/react-jwt.feature
+++ b/src/test/features/client/react-jwt.feature
@@ -3,6 +3,7 @@ Feature: React JWT
   Scenario: Should apply react jwt module to react
     When I apply modules to default project
       | init       |
+      | typescript |
       | react-core |
       | react-jwt  |
     Then I should have files in "src/main/webapp/app/common/services"
diff --git a/src/test/features/client/react.feature b/src/test/features/client/react.feature
index ebb798ee9..51e6071f3 100644
--- a/src/test/features/client/react.feature
+++ b/src/test/features/client/react.feature
@@ -4,6 +4,7 @@ Feature: React modules
     When I apply modules to default project
       | init       |
       | prettier   |
+      | typescript |
       | react-core |
     Then I should have files in "src/main/webapp/app/common/primary/app"
       | App.tsx |
diff --git a/src/test/java/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.java
index 46c5f0316..778052bab 100644
--- a/src/test/java/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.java
+++ b/src/test/java/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.java
@@ -19,45 +19,29 @@ class ReactCoreModulesFactoryTest {
       JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).projectBaseName("jhipster").build()
     );
 
-    assertThatModuleWithFiles(module, packageJsonFile(), lintStagedConfigFile())
+    assertThatModuleWithFiles(module, packageJsonFile(), lintStagedConfigFile(), eslintConfigFile(), tsConfigFile(), vitestConfigFile())
       .hasFile("package.json")
-      .containing("\"type\": \"module\"")
+      .notContaining(nodeDependency("@tsconfig/recommended"))
       .containing(nodeDependency("@testing-library/dom"))
       .containing(nodeDependency("@testing-library/react"))
       .containing(nodeDependency("@types/node"))
       .containing(nodeDependency("@types/react"))
       .containing(nodeDependency("@types/react-dom"))
       .containing(nodeDependency("@tsconfig/vite-react"))
-      .containing(nodeDependency("@typescript-eslint/eslint-plugin"))
       .containing(nodeDependency("@vitejs/plugin-react"))
-      .containing(nodeDependency("@vitest/coverage-istanbul"))
-      .containing(nodeDependency("typescript-eslint"))
-      .containing(nodeDependency("globals"))
-      .containing(nodeDependency("eslint"))
       .containing(nodeDependency("eslint-plugin-react"))
       .containing(nodeDependency("jsdom"))
-      .containing(nodeDependency("typescript"))
       .containing(nodeDependency("ts-node"))
       .containing(nodeDependency("vite"))
-      .containing(nodeDependency("vite-tsconfig-paths"))
-      .containing(nodeDependency("vitest"))
-      .containing(nodeDependency("vitest-sonar-reporter"))
       .containing(nodeDependency("react"))
-      .containing(nodeDependency("npm-run-all2"))
       .containing(nodeDependency("react-dom"))
       .containing(nodeScript("dev", "npm-run-all dev:*"))
       .containing(nodeScript("dev:vite", "vite"))
       .containing(nodeScript("build", "npm-run-all build:*"))
       .containing(nodeScript("build:tsc", "tsc"))
       .containing(nodeScript("build:vite", "vite build --emptyOutDir"))
-      .containing(nodeScript("watch", "npm-run-all --parallel watch:*"))
-      .containing(nodeScript("watch:tsc", "tsc --noEmit --watch"))
       .containing(nodeScript("preview", "vite preview"))
       .containing(nodeScript("start", "vite"))
-      .containing(nodeScript("lint", "eslint ."))
-      .containing(nodeScript("test", "npm run watch:test"))
-      .containing(nodeScript("test:coverage", "vitest run --coverage"))
-      .containing(nodeScript("watch:test", "vitest --"))
       .and()
       .hasFile(".lintstagedrc.cjs")
       .containing(
@@ -69,7 +53,16 @@ class ReactCoreModulesFactoryTest {
         """
       )
       .and()
-      .hasFiles("tsconfig.json", "vite.config.ts", "vitest.config.ts", "eslint.config.js")
+      .hasFile("eslint.config.js")
+      .matchingSavedSnapshot()
+      .and()
+      .hasFile("tsconfig.json")
+      .matchingSavedSnapshot()
+      .and()
+      .hasFile("vitest.config.ts")
+      .matchingSavedSnapshot()
+      .and()
+      .hasFiles("vite.config.ts")
       .hasFiles("src/main/webapp/index.html")
       .hasPrefixedFiles("src/main/webapp/app", "index.css", "index.tsx", "vite-env.d.ts")
       .hasFiles("src/test/webapp/unit/common/primary/app/App.spec.tsx")
@@ -86,7 +79,7 @@ class ReactCoreModulesFactoryTest {
       JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).projectBaseName("jhipster").put("serverPort", 8081).build()
     );
 
-    assertThatModuleWithFiles(module, packageJsonFile())
+    assertThatModuleWithFiles(module, packageJsonFile(), eslintConfigFile(), tsConfigFile(), vitestConfigFile())
       .hasFile("vite.config.ts")
       .containing("localhost:8081")
       .notContaining("localhost:8080");
@@ -98,7 +91,9 @@ class ReactCoreModulesFactoryTest {
       JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).projectBaseName("jhipster").build()
     );
 
-    assertThatModuleWithFiles(module, packageJsonFile()).hasFile("vite.config.ts").containing("localhost:8080");
+    assertThatModuleWithFiles(module, packageJsonFile(), eslintConfigFile(), tsConfigFile(), vitestConfigFile())
+      .hasFile("vite.config.ts")
+      .containing("localhost:8080");
   }
 
   private String nodeScript(String key, String value) {
diff --git a/src/main/resources/generator/client/react/core/vitest.config.ts.mustache b/src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.vitest.config.ts.approved.txt
similarity index 87%
rename from src/main/resources/generator/client/react/core/vitest.config.ts.mustache
rename to src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.vitest.config.ts.approved.txt
index 8fc2f51dd..5bdbdda98 100644
--- a/src/main/resources/generator/client/react/core/vitest.config.ts.mustache
+++ b/src/test/resources/tech/jhipster/lite/generator/client/react/core/domain/ReactCoreModulesFactoryTest.shouldBuildModuleWithStyle.vitest.config.ts.approved.txt
@@ -9,7 +9,7 @@ export default defineConfig({
   test: {
     reporters: ['verbose', 'vitest-sonar-reporter'],
     outputFile: {
-      'vitest-sonar-reporter': '{{projectBuildDirectory}}/test-results/TESTS-results-sonar.xml',
+      'vitest-sonar-reporter': 'target/test-results/TESTS-results-sonar.xml',
     },
     globals: true,
     logHeapUsage: true,
@@ -34,7 +34,7 @@ export default defineConfig({
         'src/main/webapp/app/index.tsx',
       ],
       provider: 'istanbul',
-      reportsDirectory: '{{projectBuildDirectory}}/test-results/',
+      reportsDirectory: 'target/test-results/',
       reporter: ['html', 'json-summary', 'text', 'text-summary', 'lcov', 'clover'],
       watermarks: {
         statements: [100, 100],
diff --git a/tests-ci/generate.sh b/tests-ci/generate.sh
index b35c79002..cc8a31acd 100755
--- a/tests-ci/generate.sh
+++ b/tests-ci/generate.sh
@@ -402,6 +402,7 @@ elif [[ $application == 'reactapp' ]]; then
   frontend_server_plugin
   applyModules \
     "prettier" \
+    "typescript" \
     "react-core" \
     "cypress-component-tests"