jest-dom icon indicating copy to clipboard operation
jest-dom copied to clipboard

TS error when using vitest globals and @testing-library/jest-dom

Open robcaldecott opened this issue 3 years ago • 18 comments

  • @testing-library/jest-dom version: 5.16.1
  • node version: 14.17.0
  • yarn version: 1.22.17
  • @testing-library/react version: 12.1.2

I am using vitest instead of jest for testing a React component lib in a monorepo. I am using @testing-library/jest-dom and @testing-library/react and both happily work with vitest.

However, when type checking my code I am seeing a clash between @types/jest and vitest. The @types/jest dependency seems to have been pulled in by @testing-library/jest-dom.

../node_modules/@types/jest/index.d.ts:34:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: test, describe, it, expect, beforeAll, afterAll, beforeEach, afterEach

34 declare var beforeAll: jest.Lifecycle;
   ~~~~~~~

  ../node_modules/vitest/global.d.ts:1:1
    1 declare global {
      ~~~~~~~
    Conflicts are in this file.

../node_modules/vitest/global.d.ts:1:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: test, describe, it, expect, beforeAll, afterAll, beforeEach, afterEach

1 declare global {
  ~~~~~~~

  ../node_modules/@types/jest/index.d.ts:34:1
    34 declare var beforeAll: jest.Lifecycle;
       ~~~~~~~
    Conflicts are in this file.


Found 2 errors.

It would be great to have a workaround for this issue so the jest types can be removed or ignored.

To reproduce this issue clone the following project (notice it uses a branch called yarn):

https://github.com/robcaldecott/pnpm-vite-monorepo-example/tree/yarn

To see the error:

yarn install
cd components
yarn build

robcaldecott avatar Jan 13 '22 12:01 robcaldecott

Related:

  • https://github.com/vitest-dev/vitest/issues/517

sheremet-va avatar Jan 14 '22 12:01 sheremet-va

Simply removing the following line from the types definition solves it and doesn't impact the functionality from my brief testing.

/// <reference types="jest" />

If anyone wants a hacky and crude solution in the meantime:

package.json

  "type": "module",
  "scripts": {
    "build": "node patchJestDom.js && %previous_build_commands%",
  }

patchJestDom.js

import fs from 'fs';
import path from 'path';

const typesPath = path.resolve('node_modules', '@types', 'testing-library__jest-dom', 'index.d.ts');

fs.readFile(typesPath, 'utf8', (err, data) => {
    if (err) throw err;

    let lines = data.split('\n');

    if (lines[8] === '/// <reference types="jest" />') {
        lines = lines.slice(0, 8).concat(lines.slice(9));
    }

    fs.writeFile(typesPath, lines.join('\n'), 'utf8', function(err) {
        if (err) throw err;
    });
});

dancras avatar Feb 11 '22 12:02 dancras

@dancras thanks for the hack, currently using it too but added to postinstall lifecycle script to have it run on both https://docs.npmjs.com/cli/v8/using-npm/scripts#npm-ci and https://docs.npmjs.com/cli/v8/using-npm/scripts#npm-install

  "scripts": {
    ...existing scripts here,
    "postinstall": "node scripts/patchJestDom.js",
  }

larsenwork avatar Feb 21 '22 11:02 larsenwork

Thanks @dancras, your solution worked! I had to modify it because my reference to jest was on line 10, so I ended up making it to recognize any line:

const fs = require("fs");
const path = require("path");

const typesPath = path.resolve(
  "node_modules",
  "@types",
  "testing-library__jest-dom",
  "index.d.ts"
);

fs.readFile(typesPath, "utf8", (err, data) => {
  if (err) throw err;

  let lines = data.split("\n");

  jestTypesIndex = lines.findIndex((line) =>
    line.includes('reference types="jest"')
  );

  if (lines[jestTypesIndex] === '/// <reference types="jest" />') {
    lines = lines
      .slice(0, jestTypesIndex)
      .concat(lines.slice(jestTypesIndex + 1));
  }

  fs.writeFile(typesPath, lines.join("\n"), "utf8", function (err) {
    if (err) throw err;
  });
});

x42a avatar Mar 12 '22 17:03 x42a

FWIW this works just fine too. (I tend not to care about throwing errors because they are of limited value in a build step like this)

import fs from 'fs'
import path from 'path'

const typesFile = path.resolve('node_modules/@types/testing-library__jest-dom/index.d.ts')
const encoding = 'utf8'
const strings = {
  search: '/// <reference types="jest" />',
  replace: '// See https://github.com/testing-library/jest-dom/issues/427 for reference',
}

const result = fs
  .readFileSync(typesFile, encoding)
  .replace(strings.search, strings.replace)

fs.writeFileSync(typesFile, result, encoding)

EDIT: my vite.config.js and tsconfig.json for reference. @laruiss ☝️

larsenwork avatar Mar 14 '22 10:03 larsenwork

@larsenwork Would you be so kind as to share your tsconfig.*.json? I am still struggling with this 😕

laruiss avatar Mar 17 '22 16:03 laruiss

@gnapse is there any possibility to remove /// <reference types="jest" /> from library types and make @types/jest a peerDependency?

Anyway, I found a slightly less hacky workaround 💯 :

setupTests.ts (file which goes to test.setupFiles in vite.config.ts)

import '@testing-library/jest-dom/extend-expect';

global.d.ts (file placed in root or inside src directory)

/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers';

declare global {
  namespace jest {
    type Matchers<R = void, T = {}> = TestingLibraryMatchers<
      typeof expect.stringContaining,
      R
    >;
  }
}

tsconfig.json

{
  "compilerOptions": {
   ...
    "types": ["node", "vite/client", "vitest/globals"],
  },
}

BPreisner avatar Apr 27 '22 13:04 BPreisner

Tried the workaround above, but had to tweak one thing. In global.d.ts I had to write this instead:

import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";

declare global {
  namespace jest {
    interface Matchers<R = void>
      extends TestingLibraryMatchers<typeof expect.stringContaining, R> {}
  }
}

airjp73 avatar May 05 '22 20:05 airjp73

Not sure if this is the correct solution but adding "skipLibCheck": true, in my tsconfig.json fixed it.

cmacdonnacha avatar May 18 '22 14:05 cmacdonnacha

for me, using [email protected] and @testing-library/[email protected] only the solution bellow worked

// global.d.ts
import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers';

type CustomMatchers<R = unknown> = TestingLibraryMatchers<typeof expect.stringContaining, R>;

declare global {
  namespace Vi {
    interface Assertion extends CustomMatchers {}
    interface AsymmetricMatchersContaining extends CustomMatchers {}
  }
}

and using pnpm the code bellow need to be placed in the .npmrc to be able to use the TestingLibraryMatchers types outside node_modules

shamefully-hoist = true

edumudu avatar Jun 29 '22 03:06 edumudu

Combining @BPreisner and @airjp73's solutions, I made a new template to save myself (and others) future time: https://github.com/jsjoeio/react-ts-vitest-template

jsjoeio avatar Jul 04 '22 15:07 jsjoeio

@BPreisner Out of curiosity, would this have worked on its own?

  "compilerOptions": {
    "typeRoots": [],
    "types": [],
    }

I know it doesn't fix the root problem of them bundling the types. But am wondering ( can't test it right now) if augmenting the jest namespace would be necessary at all if types settings are set to not include by default.

seivan avatar Jul 06 '22 06:07 seivan

I made a simple solution to fix this issue: https://github.com/zoontek/types-testing-library-vitest-dom

Installation (yarn)

"resolutions": {
  "@types/testing-library__jest-dom": "github:zoontek/types-testing-library-vitest-dom"
}

Installation (npm)

"overrides": {
  "@types/testing-library__jest-dom": "github:zoontek/types-testing-library-vitest-dom"
}

Test setup file

import matchers from "@testing-library/jest-dom/matchers";
import { expect } from "vitest";

expect.extend(matchers);

zoontek avatar Jul 07 '22 12:07 zoontek

Hi. How about providing @testing-library/vitest-dom ? Since the release of Vitest, the number of downloads of Vitest has increased. I think the official support for using Testing Library with Vitest makes us very happy and we don't have to add ad-hoc solutions.

kaorun343 avatar Aug 16 '22 18:08 kaorun343

I don't think making a new package is worth the cognitive/maintenance overhead when we could just make this package more framework agnostic for now.

However, I vaguely remember another maintainer planning on refactoring matchers out of this package. Please let me know if there's an update on that. I couldn't find the source, but I don't want to step on any toes accidentally.

nickserv avatar Sep 19 '22 09:09 nickserv

Would the workaround here work for this? https://github.com/testing-library/jest-dom/issues/439

AndrewLeedham avatar Sep 21 '22 08:09 AndrewLeedham

Are you suggesting we document this as a temporary workaround for Vitest users, or expose a Vitest entry point permanently?

nickserv avatar Sep 21 '22 08:09 nickserv

Are you suggesting we document this as a temporary workaround for Vitest users, or expose a Vitest entry point permanently?

Currently a workaround, I can see why the requirement of types: [] would be a pain, I was already using that to get around Cypress conflicts, but not an ideal solution. Maybe the inverse would be a cleaner solution, where we provide jest.d.ts and vitest.d.ts and the matchers are just exported from the index, so you have to do something like /// <reference types="@testing-library/jest-dom/vitest" /> in your jest-setup.ts script instead of excluding the index with types: []?

AndrewLeedham avatar Sep 21 '22 09:09 AndrewLeedham

Maybe the inverse would be a cleaner solution, where we provide jest.d.ts and vitest.d.ts

I like this approach as well.

For now, I am using a workaround with pnpm patch, creating this diff to empty the definitions in @types/testing-library__jest-dom/index.d.ts:

diff --git a/index.d.ts b/index.d.ts
index 43ba6b7fe458e77d152fe0b2f7afeac05d8fc563..dfa540aa7d1ab8c78c31104d3210eee3c33a94c0 100755
--- a/index.d.ts
+++ b/index.d.ts
@@ -7,12 +7,9 @@
 // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
 // Minimum TypeScript Version: 4.3
 
-/// <reference types="jest" />
+// NOTE: these definitions are left blank, to support using Testing Library Jest matchers with
+// Vitest, without having to use Jest's global types (for `expect`, etc).
 
-import { TestingLibraryMatchers } from './matchers';
+// Vitest's `expect` type is instead extended in `src/setupTests.ts`.
 
-declare global {
-    namespace jest {
-        interface Matchers<R = void, T = {}> extends TestingLibraryMatchers<typeof expect.stringContaining, R> {}
-    }
-}
+// For more context, see https://github.com/testing-library/jest-dom/issues/427

In patches/@[email protected], with this in package.json:

"pnpm": {
  "patchedDependencies": {
    "@types/[email protected]": "patches/@[email protected]"
  }
}

Paired with this in my Vitest setup file: https://github.com/testing-library/jest-dom/issues/439#issuecomment-1087504347

import matchers, {
  TestingLibraryMatchers,
} from '@testing-library/jest-dom/matchers';

declare global {
  namespace Vi {
    interface JestAssertion<T = any>
      extends jest.Matchers<void, T>,
        TestingLibraryMatchers<T, void> {}
  }
}

expect.extend(matchers);

elliottsj avatar Sep 30 '22 20:09 elliottsj

The workaround (hack 😆) that worked for me was to copy globals.d.ts from vitest to my global.d.ts file

declare global {
	const suite: typeof import('vitest')['suite']
	const test: typeof import('vitest')['test']
	const describe: typeof import('vitest')['describe']
	const it: typeof import('vitest')['it']
	const expect: typeof import('vitest')['expect']
	const assert: typeof import('vitest')['assert']
	const vitest: typeof import('vitest')['vitest']
	const vi: typeof import('vitest')['vitest']
	const beforeAll: typeof import('vitest')['beforeAll']
	const afterAll: typeof import('vitest')['afterAll']
	const beforeEach: typeof import('vitest')['beforeEach']
	const afterEach: typeof import('vitest')['afterEach']
  }

And adding the following to compilerOptions in my tsconfig:

"paths": {
		"@jest/*": ["NOT_FOUND"],
		"jest/*": ["NOT_FOUND"],
}

haikyuu avatar Oct 21 '22 15:10 haikyuu

Solution for pnpm users:

Add this to your package.json:

  "pnpm": {
    "patchedDependencies": {
      "@types/[email protected]": "patches/@[email protected]"
    }
  }

Now, create a file patches/@[email protected] with the following contents:

diff --git a/index.d.ts b/index.d.ts
index 43ba6b7fe458e77d152fe0b2f7afeac05d8fc563..3bf91587abf21f15ac166bcae1f5a59b23884b87 100755
--- a/index.d.ts
+++ b/index.d.ts
@@ -7,7 +7,7 @@
 // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
 // Minimum TypeScript Version: 4.3
 
-/// <reference types="jest" />
+// See https://github.com/testing-library/jest-dom/issues/427 for reference
 
 import { TestingLibraryMatchers } from './matchers';

Save both and run pnpm i.

typeofweb avatar Feb 07 '23 14:02 typeofweb

Do you have any updates on the status of this issue? For now, the approach I've followed is the one mentioned into another issue (which targets the latest version of Vite)

edufarre avatar May 17 '23 15:05 edufarre

Another solution if you don't mind to install a new package is to install vitest-dom. no hack or workaround and it works straightforward with pnpm too. It is not part of testing-library though.

madmadi avatar May 28 '23 14:05 madmadi

I'm also seeing this issue on a brand new project

https://github.com/merlinstardust/coverage-zero-vitest-example/actions/runs/5384546869/jobs/9772635711#step:7:20

merlinstardust avatar Jun 27 '23 05:06 merlinstardust

:tada: This issue has been resolved in version 6.0.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

github-actions[bot] avatar Aug 13 '23 16:08 github-actions[bot]