From bdb34f12959578c77b18b0c0910d512768b20ab0 Mon Sep 17 00:00:00 2001 From: Michael Manzinger Date: Fri, 18 Aug 2023 07:24:56 +0200 Subject: [PATCH] fix: matchers type is making the global expect unsafe (#513) * fix: matchers type is making the global expect unsafe * Add test file for TypeScript typings * Remove jest specifics from matchers.d.ts * Type tests for all test environments * Fix all AsymmetricMatcher interfaces * Ignore type tests from eslint --------- Co-authored-by: Michael Manzinger Co-authored-by: John Gozde --- package.json | 6 +- tsconfig.json | 4 +- .../jest-globals-custom-expect-types.test.ts | 97 ++++++++++++++ .../jest-globals/jest-globals-types.test.ts | 118 ++++++++++++++++++ types/__tests__/jest-globals/tsconfig.json | 9 ++ .../jest/jest-custom-expect-types.test.ts | 96 ++++++++++++++ types/__tests__/jest/jest-types.test.ts | 117 +++++++++++++++++ types/__tests__/jest/tsconfig.json | 9 ++ types/__tests__/vitest/tsconfig.json | 9 ++ .../vitest/vitest-custom-expect-types.test.ts | 97 ++++++++++++++ types/__tests__/vitest/vitest-types.test.ts | 118 ++++++++++++++++++ types/jest-globals.d.ts | 5 +- types/jest.d.ts | 5 +- types/matchers.d.ts | 7 +- types/vitest.d.ts | 5 +- 15 files changed, 694 insertions(+), 8 deletions(-) create mode 100644 types/__tests__/jest-globals/jest-globals-custom-expect-types.test.ts create mode 100644 types/__tests__/jest-globals/jest-globals-types.test.ts create mode 100644 types/__tests__/jest-globals/tsconfig.json create mode 100644 types/__tests__/jest/jest-custom-expect-types.test.ts create mode 100644 types/__tests__/jest/jest-types.test.ts create mode 100644 types/__tests__/jest/tsconfig.json create mode 100644 types/__tests__/vitest/tsconfig.json create mode 100644 types/__tests__/vitest/vitest-custom-expect-types.test.ts create mode 100644 types/__tests__/vitest/vitest-types.test.ts diff --git a/package.json b/package.json index f8346ee5..c329a7d3 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "setup": "npm install && npm run validate -s", "test": "kcd-scripts test", "test:update": "npm test -- --updateSnapshot --coverage", - "validate": "kcd-scripts validate" + "test:types": "tsc -p types/__tests__/jest && tsc -p types/__tests__/jest-globals && tsc -p types/__tests__/vitest", + "validate": "kcd-scripts validate && npm run test:types" }, "files": [ "dist", @@ -110,7 +111,8 @@ "eslintIgnore": [ "node_modules", "coverage", - "dist" + "dist", + "types/__tests__" ], "repository": { "type": "git", diff --git a/tsconfig.json b/tsconfig.json index 9a426aba..dec023f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,9 @@ { "compilerOptions": { + "noEmit": true, "strict": true, "skipLibCheck": true }, - "include": ["*.d.ts", "types"] + "include": ["*.d.ts", "types"], + "exclude": ["types/__tests__"] } diff --git a/types/__tests__/jest-globals/jest-globals-custom-expect-types.test.ts b/types/__tests__/jest-globals/jest-globals-custom-expect-types.test.ts new file mode 100644 index 00000000..96034dc1 --- /dev/null +++ b/types/__tests__/jest-globals/jest-globals-custom-expect-types.test.ts @@ -0,0 +1,97 @@ +/** + * File that tests whether the TypeScript typings work as expected. + */ + +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import {expect} from '@jest/globals' +import * as matchers from '../../matchers' + +expect.extend(matchers) + +const element: HTMLElement = document.body + +function customExpect( + _actual: HTMLElement, +): + | matchers.TestingLibraryMatchers + | matchers.TestingLibraryMatchers> { + throw new Error('Method not implemented.') +} + +customExpect(element).toBeInTheDOM() +customExpect(element).toBeInTheDOM(document.body) +customExpect(element).toBeInTheDocument() +customExpect(element).toBeVisible() +customExpect(element).toBeEmpty() +customExpect(element).toBeDisabled() +customExpect(element).toBeEnabled() +customExpect(element).toBeInvalid() +customExpect(element).toBeRequired() +customExpect(element).toBeValid() +customExpect(element).toContainElement(document.body) +customExpect(element).toContainElement(null) +customExpect(element).toContainHTML('body') +customExpect(element).toHaveAttribute('attr') +customExpect(element).toHaveAttribute('attr', true) +customExpect(element).toHaveAttribute('attr', 'yes') +customExpect(element).toHaveClass() +customExpect(element).toHaveClass('cls1') +customExpect(element).toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +customExpect(element).toHaveClass('cls1', {exact: true}) +customExpect(element).toHaveDisplayValue('str') +customExpect(element).toHaveDisplayValue(['str1', 'str2']) +customExpect(element).toHaveDisplayValue(/str/) +customExpect(element).toHaveDisplayValue([/str1/, 'str2']) +customExpect(element).toHaveFocus() +customExpect(element).toHaveFormValues({foo: 'bar', baz: 1}) +customExpect(element).toHaveStyle('display: block') +customExpect(element).toHaveStyle({display: 'block', width: 100}) +customExpect(element).toHaveTextContent('Text') +customExpect(element).toHaveTextContent(/Text/) +customExpect(element).toHaveTextContent('Text', {normalizeWhitespace: true}) +customExpect(element).toHaveTextContent(/Text/, {normalizeWhitespace: true}) +customExpect(element).toHaveValue() +customExpect(element).toHaveValue('str') +customExpect(element).toHaveValue(['str1', 'str2']) +customExpect(element).toHaveValue(1) +customExpect(element).toHaveValue(null) +customExpect(element).toBeChecked() +customExpect(element).toHaveDescription('some description') +customExpect(element).toHaveDescription(/some description/) +customExpect(element).toHaveDescription(expect.stringContaining('partial')) +customExpect(element).toHaveDescription() +customExpect(element).toHaveAccessibleDescription('some description') +customExpect(element).toHaveAccessibleDescription(/some description/) +customExpect(element).toHaveAccessibleDescription( + expect.stringContaining('partial'), +) +customExpect(element).toHaveAccessibleDescription() + +customExpect(element).toHaveAccessibleErrorMessage() +customExpect(element).toHaveAccessibleErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +customExpect(element).toHaveAccessibleErrorMessage(/invalid time/i) +customExpect(element).toHaveAccessibleErrorMessage( + expect.stringContaining('Invalid time'), +) + +customExpect(element).toHaveAccessibleName('a label') +customExpect(element).toHaveAccessibleName(/a label/) +customExpect(element).toHaveAccessibleName( + expect.stringContaining('partial label'), +) +customExpect(element).toHaveAccessibleName() +customExpect(element).toHaveErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +customExpect(element).toHaveErrorMessage(/invalid time/i) +customExpect(element).toHaveErrorMessage( + expect.stringContaining('Invalid time'), +) + +// @ts-expect-error The types accidentally allowed any property by falling back to "any" +customExpect(element).nonExistentProperty() diff --git a/types/__tests__/jest-globals/jest-globals-types.test.ts b/types/__tests__/jest-globals/jest-globals-types.test.ts new file mode 100644 index 00000000..645f44ec --- /dev/null +++ b/types/__tests__/jest-globals/jest-globals-types.test.ts @@ -0,0 +1,118 @@ +/** + * File that tests whether the TypeScript typings for @types/jest work as expected. + */ + +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import {expect} from '@jest/globals' +import '../../jest-globals' + +const element: HTMLElement = document.body + +expect(element).toBeInTheDOM() +expect(element).toBeInTheDOM(document.body) +expect(element).toBeInTheDocument() +expect(element).toBeVisible() +expect(element).toBeEmpty() +expect(element).toBeDisabled() +expect(element).toBeEnabled() +expect(element).toBeInvalid() +expect(element).toBeRequired() +expect(element).toBeValid() +expect(element).toContainElement(document.body) +expect(element).toContainElement(null) +expect(element).toContainHTML('body') +expect(element).toHaveAttribute('attr') +expect(element).toHaveAttribute('attr', true) +expect(element).toHaveAttribute('attr', 'yes') +expect(element).toHaveClass() +expect(element).toHaveClass('cls1') +expect(element).toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +expect(element).toHaveClass('cls1', {exact: true}) +expect(element).toHaveDisplayValue('str') +expect(element).toHaveDisplayValue(['str1', 'str2']) +expect(element).toHaveDisplayValue(/str/) +expect(element).toHaveDisplayValue([/str1/, 'str2']) +expect(element).toHaveFocus() +expect(element).toHaveFormValues({foo: 'bar', baz: 1}) +expect(element).toHaveStyle('display: block') +expect(element).toHaveStyle({display: 'block', width: 100}) +expect(element).toHaveTextContent('Text') +expect(element).toHaveTextContent(/Text/) +expect(element).toHaveTextContent('Text', {normalizeWhitespace: true}) +expect(element).toHaveTextContent(/Text/, {normalizeWhitespace: true}) +expect(element).toHaveValue() +expect(element).toHaveValue('str') +expect(element).toHaveValue(['str1', 'str2']) +expect(element).toHaveValue(1) +expect(element).toHaveValue(null) +expect(element).toBeChecked() +expect(element).toHaveDescription('some description') +expect(element).toHaveDescription(/some description/) +expect(element).toHaveDescription(expect.stringContaining('partial')) +expect(element).toHaveDescription() +expect(element).toHaveAccessibleDescription('some description') +expect(element).toHaveAccessibleDescription(/some description/) +expect(element).toHaveAccessibleDescription(expect.stringContaining('partial')) +expect(element).toHaveAccessibleDescription() +expect(element).toHaveAccessibleName('a label') +expect(element).toHaveAccessibleName(/a label/) +expect(element).toHaveAccessibleName(expect.stringContaining('partial label')) +expect(element).toHaveAccessibleName() +expect(element).toHaveErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +expect(element).toHaveErrorMessage(/invalid time/i) +expect(element).toHaveErrorMessage(expect.stringContaining('Invalid time')) + +expect(element).not.toBeInTheDOM() +expect(element).not.toBeInTheDOM(document.body) +expect(element).not.toBeInTheDocument() +expect(element).not.toBeVisible() +expect(element).not.toBeEmpty() +expect(element).not.toBeEmptyDOMElement() +expect(element).not.toBeDisabled() +expect(element).not.toBeEnabled() +expect(element).not.toBeInvalid() +expect(element).not.toBeRequired() +expect(element).not.toBeValid() +expect(element).not.toContainElement(document.body) +expect(element).not.toContainElement(null) +expect(element).not.toContainHTML('body') +expect(element).not.toHaveAttribute('attr') +expect(element).not.toHaveAttribute('attr', true) +expect(element).not.toHaveAttribute('attr', 'yes') +expect(element).not.toHaveClass() +expect(element).not.toHaveClass('cls1') +expect(element).not.toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +expect(element).not.toHaveClass('cls1', {exact: true}) +expect(element).not.toHaveDisplayValue('str') +expect(element).not.toHaveDisplayValue(['str1', 'str2']) +expect(element).not.toHaveDisplayValue(/str/) +expect(element).not.toHaveDisplayValue([/str1/, 'str2']) +expect(element).not.toHaveFocus() +expect(element).not.toHaveFormValues({foo: 'bar', baz: 1}) +expect(element).not.toHaveStyle('display: block') +expect(element).not.toHaveTextContent('Text') +expect(element).not.toHaveTextContent(/Text/) +expect(element).not.toHaveTextContent('Text', {normalizeWhitespace: true}) +expect(element).not.toHaveTextContent(/Text/, {normalizeWhitespace: true}) +expect(element).not.toHaveValue() +expect(element).not.toHaveValue('str') +expect(element).not.toHaveValue(['str1', 'str2']) +expect(element).not.toHaveValue(1) +expect(element).not.toBeChecked() +expect(element).not.toHaveDescription('some description') +expect(element).not.toHaveDescription() +expect(element).not.toHaveAccessibleDescription('some description') +expect(element).not.toHaveAccessibleDescription() +expect(element).not.toHaveAccessibleName('a label') +expect(element).not.toHaveAccessibleName() +expect(element).not.toBePartiallyChecked() +expect(element).not.toHaveErrorMessage() +expect(element).not.toHaveErrorMessage('Pikachu!') + +// @ts-expect-error The types accidentally allowed any property by falling back to "any" +expect(element).nonExistentProperty() diff --git a/types/__tests__/jest-globals/tsconfig.json b/types/__tests__/jest-globals/tsconfig.json new file mode 100644 index 00000000..25a9cf27 --- /dev/null +++ b/types/__tests__/jest-globals/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "types": [] + }, + "include": ["*.ts"] +} diff --git a/types/__tests__/jest/jest-custom-expect-types.test.ts b/types/__tests__/jest/jest-custom-expect-types.test.ts new file mode 100644 index 00000000..c3ac5a7d --- /dev/null +++ b/types/__tests__/jest/jest-custom-expect-types.test.ts @@ -0,0 +1,96 @@ +/** + * File that tests whether the TypeScript typings work as expected. + */ + +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import * as matchers from '../../matchers' + +expect.extend(matchers) + +const element: HTMLElement = document.body + +function customExpect( + _actual: HTMLElement, +): + | matchers.TestingLibraryMatchers + | matchers.TestingLibraryMatchers> { + throw new Error('Method not implemented.') +} + +customExpect(element).toBeInTheDOM() +customExpect(element).toBeInTheDOM(document.body) +customExpect(element).toBeInTheDocument() +customExpect(element).toBeVisible() +customExpect(element).toBeEmpty() +customExpect(element).toBeDisabled() +customExpect(element).toBeEnabled() +customExpect(element).toBeInvalid() +customExpect(element).toBeRequired() +customExpect(element).toBeValid() +customExpect(element).toContainElement(document.body) +customExpect(element).toContainElement(null) +customExpect(element).toContainHTML('body') +customExpect(element).toHaveAttribute('attr') +customExpect(element).toHaveAttribute('attr', true) +customExpect(element).toHaveAttribute('attr', 'yes') +customExpect(element).toHaveClass() +customExpect(element).toHaveClass('cls1') +customExpect(element).toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +customExpect(element).toHaveClass('cls1', {exact: true}) +customExpect(element).toHaveDisplayValue('str') +customExpect(element).toHaveDisplayValue(['str1', 'str2']) +customExpect(element).toHaveDisplayValue(/str/) +customExpect(element).toHaveDisplayValue([/str1/, 'str2']) +customExpect(element).toHaveFocus() +customExpect(element).toHaveFormValues({foo: 'bar', baz: 1}) +customExpect(element).toHaveStyle('display: block') +customExpect(element).toHaveStyle({display: 'block', width: 100}) +customExpect(element).toHaveTextContent('Text') +customExpect(element).toHaveTextContent(/Text/) +customExpect(element).toHaveTextContent('Text', {normalizeWhitespace: true}) +customExpect(element).toHaveTextContent(/Text/, {normalizeWhitespace: true}) +customExpect(element).toHaveValue() +customExpect(element).toHaveValue('str') +customExpect(element).toHaveValue(['str1', 'str2']) +customExpect(element).toHaveValue(1) +customExpect(element).toHaveValue(null) +customExpect(element).toBeChecked() +customExpect(element).toHaveDescription('some description') +customExpect(element).toHaveDescription(/some description/) +customExpect(element).toHaveDescription(expect.stringContaining('partial')) +customExpect(element).toHaveDescription() +customExpect(element).toHaveAccessibleDescription('some description') +customExpect(element).toHaveAccessibleDescription(/some description/) +customExpect(element).toHaveAccessibleDescription( + expect.stringContaining('partial'), +) +customExpect(element).toHaveAccessibleDescription() + +customExpect(element).toHaveAccessibleErrorMessage() +customExpect(element).toHaveAccessibleErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +customExpect(element).toHaveAccessibleErrorMessage(/invalid time/i) +customExpect(element).toHaveAccessibleErrorMessage( + expect.stringContaining('Invalid time'), +) + +customExpect(element).toHaveAccessibleName('a label') +customExpect(element).toHaveAccessibleName(/a label/) +customExpect(element).toHaveAccessibleName( + expect.stringContaining('partial label'), +) +customExpect(element).toHaveAccessibleName() +customExpect(element).toHaveErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +customExpect(element).toHaveErrorMessage(/invalid time/i) +customExpect(element).toHaveErrorMessage( + expect.stringContaining('Invalid time'), +) + +// @ts-expect-error The types accidentally allowed any property by falling back to "any" +customExpect(element).nonExistentProperty() diff --git a/types/__tests__/jest/jest-types.test.ts b/types/__tests__/jest/jest-types.test.ts new file mode 100644 index 00000000..404b9881 --- /dev/null +++ b/types/__tests__/jest/jest-types.test.ts @@ -0,0 +1,117 @@ +/** + * File that tests whether the TypeScript typings for @types/jest work as expected. + */ + +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import '../../jest' + +const element: HTMLElement = document.body + +expect(element).toBeInTheDOM() +expect(element).toBeInTheDOM(document.body) +expect(element).toBeInTheDocument() +expect(element).toBeVisible() +expect(element).toBeEmpty() +expect(element).toBeDisabled() +expect(element).toBeEnabled() +expect(element).toBeInvalid() +expect(element).toBeRequired() +expect(element).toBeValid() +expect(element).toContainElement(document.body) +expect(element).toContainElement(null) +expect(element).toContainHTML('body') +expect(element).toHaveAttribute('attr') +expect(element).toHaveAttribute('attr', true) +expect(element).toHaveAttribute('attr', 'yes') +expect(element).toHaveClass() +expect(element).toHaveClass('cls1') +expect(element).toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +expect(element).toHaveClass('cls1', {exact: true}) +expect(element).toHaveDisplayValue('str') +expect(element).toHaveDisplayValue(['str1', 'str2']) +expect(element).toHaveDisplayValue(/str/) +expect(element).toHaveDisplayValue([/str1/, 'str2']) +expect(element).toHaveFocus() +expect(element).toHaveFormValues({foo: 'bar', baz: 1}) +expect(element).toHaveStyle('display: block') +expect(element).toHaveStyle({display: 'block', width: 100}) +expect(element).toHaveTextContent('Text') +expect(element).toHaveTextContent(/Text/) +expect(element).toHaveTextContent('Text', {normalizeWhitespace: true}) +expect(element).toHaveTextContent(/Text/, {normalizeWhitespace: true}) +expect(element).toHaveValue() +expect(element).toHaveValue('str') +expect(element).toHaveValue(['str1', 'str2']) +expect(element).toHaveValue(1) +expect(element).toHaveValue(null) +expect(element).toBeChecked() +expect(element).toHaveDescription('some description') +expect(element).toHaveDescription(/some description/) +expect(element).toHaveDescription(expect.stringContaining('partial')) +expect(element).toHaveDescription() +expect(element).toHaveAccessibleDescription('some description') +expect(element).toHaveAccessibleDescription(/some description/) +expect(element).toHaveAccessibleDescription(expect.stringContaining('partial')) +expect(element).toHaveAccessibleDescription() +expect(element).toHaveAccessibleName('a label') +expect(element).toHaveAccessibleName(/a label/) +expect(element).toHaveAccessibleName(expect.stringContaining('partial label')) +expect(element).toHaveAccessibleName() +expect(element).toHaveErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +expect(element).toHaveErrorMessage(/invalid time/i) +expect(element).toHaveErrorMessage(expect.stringContaining('Invalid time')) + +expect(element).not.toBeInTheDOM() +expect(element).not.toBeInTheDOM(document.body) +expect(element).not.toBeInTheDocument() +expect(element).not.toBeVisible() +expect(element).not.toBeEmpty() +expect(element).not.toBeEmptyDOMElement() +expect(element).not.toBeDisabled() +expect(element).not.toBeEnabled() +expect(element).not.toBeInvalid() +expect(element).not.toBeRequired() +expect(element).not.toBeValid() +expect(element).not.toContainElement(document.body) +expect(element).not.toContainElement(null) +expect(element).not.toContainHTML('body') +expect(element).not.toHaveAttribute('attr') +expect(element).not.toHaveAttribute('attr', true) +expect(element).not.toHaveAttribute('attr', 'yes') +expect(element).not.toHaveClass() +expect(element).not.toHaveClass('cls1') +expect(element).not.toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +expect(element).not.toHaveClass('cls1', {exact: true}) +expect(element).not.toHaveDisplayValue('str') +expect(element).not.toHaveDisplayValue(['str1', 'str2']) +expect(element).not.toHaveDisplayValue(/str/) +expect(element).not.toHaveDisplayValue([/str1/, 'str2']) +expect(element).not.toHaveFocus() +expect(element).not.toHaveFormValues({foo: 'bar', baz: 1}) +expect(element).not.toHaveStyle('display: block') +expect(element).not.toHaveTextContent('Text') +expect(element).not.toHaveTextContent(/Text/) +expect(element).not.toHaveTextContent('Text', {normalizeWhitespace: true}) +expect(element).not.toHaveTextContent(/Text/, {normalizeWhitespace: true}) +expect(element).not.toHaveValue() +expect(element).not.toHaveValue('str') +expect(element).not.toHaveValue(['str1', 'str2']) +expect(element).not.toHaveValue(1) +expect(element).not.toBeChecked() +expect(element).not.toHaveDescription('some description') +expect(element).not.toHaveDescription() +expect(element).not.toHaveAccessibleDescription('some description') +expect(element).not.toHaveAccessibleDescription() +expect(element).not.toHaveAccessibleName('a label') +expect(element).not.toHaveAccessibleName() +expect(element).not.toBePartiallyChecked() +expect(element).not.toHaveErrorMessage() +expect(element).not.toHaveErrorMessage('Pikachu!') + +// @ts-expect-error The types accidentally allowed any property by falling back to "any" +expect(element).nonExistentProperty() diff --git a/types/__tests__/jest/tsconfig.json b/types/__tests__/jest/tsconfig.json new file mode 100644 index 00000000..52ecd5d4 --- /dev/null +++ b/types/__tests__/jest/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "types": ["jest"] + }, + "include": ["*.ts"] +} diff --git a/types/__tests__/vitest/tsconfig.json b/types/__tests__/vitest/tsconfig.json new file mode 100644 index 00000000..25a9cf27 --- /dev/null +++ b/types/__tests__/vitest/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "types": [] + }, + "include": ["*.ts"] +} diff --git a/types/__tests__/vitest/vitest-custom-expect-types.test.ts b/types/__tests__/vitest/vitest-custom-expect-types.test.ts new file mode 100644 index 00000000..e9008966 --- /dev/null +++ b/types/__tests__/vitest/vitest-custom-expect-types.test.ts @@ -0,0 +1,97 @@ +/** + * File that tests whether the TypeScript typings work as expected. + */ + +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import {expect} from 'vitest' +import * as matchers from '../../matchers' + +expect.extend(matchers) + +const element: HTMLElement = document.body + +function customExpect( + _actual: HTMLElement, +): + | matchers.TestingLibraryMatchers + | matchers.TestingLibraryMatchers> { + throw new Error('Method not implemented.') +} + +customExpect(element).toBeInTheDOM() +customExpect(element).toBeInTheDOM(document.body) +customExpect(element).toBeInTheDocument() +customExpect(element).toBeVisible() +customExpect(element).toBeEmpty() +customExpect(element).toBeDisabled() +customExpect(element).toBeEnabled() +customExpect(element).toBeInvalid() +customExpect(element).toBeRequired() +customExpect(element).toBeValid() +customExpect(element).toContainElement(document.body) +customExpect(element).toContainElement(null) +customExpect(element).toContainHTML('body') +customExpect(element).toHaveAttribute('attr') +customExpect(element).toHaveAttribute('attr', true) +customExpect(element).toHaveAttribute('attr', 'yes') +customExpect(element).toHaveClass() +customExpect(element).toHaveClass('cls1') +customExpect(element).toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +customExpect(element).toHaveClass('cls1', {exact: true}) +customExpect(element).toHaveDisplayValue('str') +customExpect(element).toHaveDisplayValue(['str1', 'str2']) +customExpect(element).toHaveDisplayValue(/str/) +customExpect(element).toHaveDisplayValue([/str1/, 'str2']) +customExpect(element).toHaveFocus() +customExpect(element).toHaveFormValues({foo: 'bar', baz: 1}) +customExpect(element).toHaveStyle('display: block') +customExpect(element).toHaveStyle({display: 'block', width: 100}) +customExpect(element).toHaveTextContent('Text') +customExpect(element).toHaveTextContent(/Text/) +customExpect(element).toHaveTextContent('Text', {normalizeWhitespace: true}) +customExpect(element).toHaveTextContent(/Text/, {normalizeWhitespace: true}) +customExpect(element).toHaveValue() +customExpect(element).toHaveValue('str') +customExpect(element).toHaveValue(['str1', 'str2']) +customExpect(element).toHaveValue(1) +customExpect(element).toHaveValue(null) +customExpect(element).toBeChecked() +customExpect(element).toHaveDescription('some description') +customExpect(element).toHaveDescription(/some description/) +customExpect(element).toHaveDescription(expect.stringContaining('partial')) +customExpect(element).toHaveDescription() +customExpect(element).toHaveAccessibleDescription('some description') +customExpect(element).toHaveAccessibleDescription(/some description/) +customExpect(element).toHaveAccessibleDescription( + expect.stringContaining('partial'), +) +customExpect(element).toHaveAccessibleDescription() + +customExpect(element).toHaveAccessibleErrorMessage() +customExpect(element).toHaveAccessibleErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +customExpect(element).toHaveAccessibleErrorMessage(/invalid time/i) +customExpect(element).toHaveAccessibleErrorMessage( + expect.stringContaining('Invalid time'), +) + +customExpect(element).toHaveAccessibleName('a label') +customExpect(element).toHaveAccessibleName(/a label/) +customExpect(element).toHaveAccessibleName( + expect.stringContaining('partial label'), +) +customExpect(element).toHaveAccessibleName() +customExpect(element).toHaveErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +customExpect(element).toHaveErrorMessage(/invalid time/i) +customExpect(element).toHaveErrorMessage( + expect.stringContaining('Invalid time'), +) + +// @ts-expect-error The types accidentally allowed any property by falling back to "any" +customExpect(element).nonExistentProperty() diff --git a/types/__tests__/vitest/vitest-types.test.ts b/types/__tests__/vitest/vitest-types.test.ts new file mode 100644 index 00000000..69f1dbc5 --- /dev/null +++ b/types/__tests__/vitest/vitest-types.test.ts @@ -0,0 +1,118 @@ +/** + * File that tests whether the TypeScript typings for @types/jest work as expected. + */ + +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import {expect} from 'vitest' +import '../../vitest' + +const element: HTMLElement = document.body + +expect(element).toBeInTheDOM() +expect(element).toBeInTheDOM(document.body) +expect(element).toBeInTheDocument() +expect(element).toBeVisible() +expect(element).toBeEmpty() +expect(element).toBeDisabled() +expect(element).toBeEnabled() +expect(element).toBeInvalid() +expect(element).toBeRequired() +expect(element).toBeValid() +expect(element).toContainElement(document.body) +expect(element).toContainElement(null) +expect(element).toContainHTML('body') +expect(element).toHaveAttribute('attr') +expect(element).toHaveAttribute('attr', true) +expect(element).toHaveAttribute('attr', 'yes') +expect(element).toHaveClass() +expect(element).toHaveClass('cls1') +expect(element).toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +expect(element).toHaveClass('cls1', {exact: true}) +expect(element).toHaveDisplayValue('str') +expect(element).toHaveDisplayValue(['str1', 'str2']) +expect(element).toHaveDisplayValue(/str/) +expect(element).toHaveDisplayValue([/str1/, 'str2']) +expect(element).toHaveFocus() +expect(element).toHaveFormValues({foo: 'bar', baz: 1}) +expect(element).toHaveStyle('display: block') +expect(element).toHaveStyle({display: 'block', width: 100}) +expect(element).toHaveTextContent('Text') +expect(element).toHaveTextContent(/Text/) +expect(element).toHaveTextContent('Text', {normalizeWhitespace: true}) +expect(element).toHaveTextContent(/Text/, {normalizeWhitespace: true}) +expect(element).toHaveValue() +expect(element).toHaveValue('str') +expect(element).toHaveValue(['str1', 'str2']) +expect(element).toHaveValue(1) +expect(element).toHaveValue(null) +expect(element).toBeChecked() +expect(element).toHaveDescription('some description') +expect(element).toHaveDescription(/some description/) +expect(element).toHaveDescription(expect.stringContaining('partial')) +expect(element).toHaveDescription() +expect(element).toHaveAccessibleDescription('some description') +expect(element).toHaveAccessibleDescription(/some description/) +expect(element).toHaveAccessibleDescription(expect.stringContaining('partial')) +expect(element).toHaveAccessibleDescription() +expect(element).toHaveAccessibleName('a label') +expect(element).toHaveAccessibleName(/a label/) +expect(element).toHaveAccessibleName(expect.stringContaining('partial label')) +expect(element).toHaveAccessibleName() +expect(element).toHaveErrorMessage( + 'Invalid time: the time must be between 9:00 AM and 5:00 PM', +) +expect(element).toHaveErrorMessage(/invalid time/i) +expect(element).toHaveErrorMessage(expect.stringContaining('Invalid time')) + +expect(element).not.toBeInTheDOM() +expect(element).not.toBeInTheDOM(document.body) +expect(element).not.toBeInTheDocument() +expect(element).not.toBeVisible() +expect(element).not.toBeEmpty() +expect(element).not.toBeEmptyDOMElement() +expect(element).not.toBeDisabled() +expect(element).not.toBeEnabled() +expect(element).not.toBeInvalid() +expect(element).not.toBeRequired() +expect(element).not.toBeValid() +expect(element).not.toContainElement(document.body) +expect(element).not.toContainElement(null) +expect(element).not.toContainHTML('body') +expect(element).not.toHaveAttribute('attr') +expect(element).not.toHaveAttribute('attr', true) +expect(element).not.toHaveAttribute('attr', 'yes') +expect(element).not.toHaveClass() +expect(element).not.toHaveClass('cls1') +expect(element).not.toHaveClass('cls1', 'cls2', 'cls3', 'cls4') +expect(element).not.toHaveClass('cls1', {exact: true}) +expect(element).not.toHaveDisplayValue('str') +expect(element).not.toHaveDisplayValue(['str1', 'str2']) +expect(element).not.toHaveDisplayValue(/str/) +expect(element).not.toHaveDisplayValue([/str1/, 'str2']) +expect(element).not.toHaveFocus() +expect(element).not.toHaveFormValues({foo: 'bar', baz: 1}) +expect(element).not.toHaveStyle('display: block') +expect(element).not.toHaveTextContent('Text') +expect(element).not.toHaveTextContent(/Text/) +expect(element).not.toHaveTextContent('Text', {normalizeWhitespace: true}) +expect(element).not.toHaveTextContent(/Text/, {normalizeWhitespace: true}) +expect(element).not.toHaveValue() +expect(element).not.toHaveValue('str') +expect(element).not.toHaveValue(['str1', 'str2']) +expect(element).not.toHaveValue(1) +expect(element).not.toBeChecked() +expect(element).not.toHaveDescription('some description') +expect(element).not.toHaveDescription() +expect(element).not.toHaveAccessibleDescription('some description') +expect(element).not.toHaveAccessibleDescription() +expect(element).not.toHaveAccessibleName('a label') +expect(element).not.toHaveAccessibleName() +expect(element).not.toBePartiallyChecked() +expect(element).not.toHaveErrorMessage() +expect(element).not.toHaveErrorMessage('Pikachu!') + +// @ts-expect-error The types accidentally allowed any property by falling back to "any" +expect(element).nonExistentProperty() diff --git a/types/jest-globals.d.ts b/types/jest-globals.d.ts index f7de8014..a6819079 100644 --- a/types/jest-globals.d.ts +++ b/types/jest-globals.d.ts @@ -4,5 +4,8 @@ import {type TestingLibraryMatchers} from './matchers' export {} declare module '@jest/expect' { export interface Matchers> - extends TestingLibraryMatchers {} + extends TestingLibraryMatchers< + ReturnType, + R + > {} } diff --git a/types/jest.d.ts b/types/jest.d.ts index bca4339c..1daed629 100644 --- a/types/jest.d.ts +++ b/types/jest.d.ts @@ -5,6 +5,9 @@ import {type TestingLibraryMatchers} from './matchers' declare global { namespace jest { interface Matchers - extends TestingLibraryMatchers {} + extends TestingLibraryMatchers< + ReturnType, + R + > {} } } diff --git a/types/matchers.d.ts b/types/matchers.d.ts index af43570d..ff9b8401 100755 --- a/types/matchers.d.ts +++ b/types/matchers.d.ts @@ -1,5 +1,5 @@ declare namespace matchers { - interface TestingLibraryMatchers extends Record { + interface TestingLibraryMatchers { /** * @deprecated * since v1.9.0 @@ -662,5 +662,8 @@ declare namespace matchers { } } -declare const matchers: matchers.TestingLibraryMatchers +// Needs to extend Record to be accepted by expect.extend() +// as it requires a string index signature. +declare const matchers: matchers.TestingLibraryMatchers & + Record export = matchers diff --git a/types/vitest.d.ts b/types/vitest.d.ts index 1eefb412..a19295be 100644 --- a/types/vitest.d.ts +++ b/types/vitest.d.ts @@ -4,5 +4,8 @@ import {type TestingLibraryMatchers} from './matchers' export {} declare module '@vitest/expect' { interface JestAssertion - extends TestingLibraryMatchers {} + extends TestingLibraryMatchers< + ReturnType, + T + > {} }