typescriptviteeslintvitesttypescript-eslint

ESLint doesn’t recognize Vitest globals when using a separate tsconfig.test.json with types: ["vitest/globals"]


I’m trying to separate my TypeScript test configs for a Vite + React + Vitest project. The tsconfig.app.json and tsconfig.node.json files were created automatically by Vite; I only added my own tsconfig.test.json.

I now have:

The problem is: ESLint does not recognize Vitest globals (describe, it, expect, etc.) in my test files. TypeScript compiles fine when running Vitest, but ESLint shows the error:

Cannot find name 'describe'. Do you need to install type definitions for a test runner?  Try npm i --save-dev @types/jest or npm i --save-dev @types/mocha.ts(2582)

Even though I already added "types": ["vitest/globals"] in my tsconfig.test.json.

My setup

tsconfig.test.json

{
  "extends": "./tsconfig.app.json",
  "compilerOptions": {
    "types": ["vitest/globals"]
  },
  "include": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/tests/setup.ts"]
}

tsconfig.json

{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" },
    { "path": "./tsconfig.test.json" }
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

vitest.config.ts

import { resolve } from 'path';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/tests/setup.ts',
    css: true,
    typecheck: {
      tsconfig: './tsconfig.test.json',
    },
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
    },
  },
});

eslint.config.ts

import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
import reactX from 'eslint-plugin-react-x';
import reactDom from 'eslint-plugin-react-dom';

export default tseslint.config(
  { ignores: ['dist'] },
  {
    extends: [
      js.configs.recommended,
      ...tseslint.configs.recommendedTypeChecked,
      ...tseslint.configs.stylisticTypeChecked,
    ],
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        project: ['./tsconfig.node.json', './tsconfig.app.json'],
        tsconfigRootDir: import.meta.dirname,
      },
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
      'react-x': reactX,
      'react-dom': reactDom,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
      ...reactX.configs['recommended-typescript'].rules,
      ...reactDom.configs.recommended.rules,
    },
  },

  // Test files configuration
  {
    files: ['src/**/*.test.{ts,tsx}', 'src/tests/setup.ts'],
    languageOptions: {
      parserOptions: {
        project: './tsconfig.test.json',
      },
    },
  },
  {
    files: ['cypress/**/*.{ts,tsx}'],
    extends: ['plugin:cypress/recommended'],
  }
);

What I tried:

How do I correctly configure ESLint + TypeScript so it recognizes Vitest globals (describe, it, etc.) from my tsconfig.test.json only without needing to add them into tsconfig.app.json?


Solution

  • Issue
    tsconfig.app.json included the whole src folder, which also contained test files. This meant the tests were type-checked by both tsconfig.app.json and tsconfig.test.json, which caused conflicts and ESLint didn’t recognize Vitest globals.

    Fix
    Exclude test files from tsconfig.app.json so only tsconfig.test.json handles them.

    tsconfig.app.json

    {
      // Vite defaults...
    
      "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/tests/setup.ts"]
    }
    

    tsconfig.test.json

    {
      "compilerOptions": {
        "types": ["vitest/globals"],
        "lib": ["ES2020", "DOM"],
        "module": "ESNext",
        "moduleResolution": "bundler",
        "jsx": "react-jsx"
      },
      "include": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/tests/setup.ts"]
    }
    

    After this change, ESLint recognized describe, it, expect, etc.

    (Optional): I also added @vitest/eslint-plugin to my ESLint config. Not required for fixing the globals error, but helpful for extra rules and best practices in tests.