Skip to content

Commit

Permalink
fix: matchers type is making the global expect unsafe (#513)
Browse files Browse the repository at this point in the history
* 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 <michael.manzinger@mobilex.eu>
Co-authored-by: John Gozde <john@gozde.ca>
  • Loading branch information
3 people authored Aug 18, 2023
1 parent 4b764b9 commit bdb34f1
Show file tree
Hide file tree
Showing 15 changed files with 694 additions and 8 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -110,7 +111,8 @@
"eslintIgnore": [
"node_modules",
"coverage",
"dist"
"dist",
"types/__tests__"
],
"repository": {
"type": "git",
Expand Down
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"compilerOptions": {
"noEmit": true,
"strict": true,
"skipLibCheck": true
},
"include": ["*.d.ts", "types"]
"include": ["*.d.ts", "types"],
"exclude": ["types/__tests__"]
}
Original file line number Diff line number Diff line change
@@ -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<unknown, void>
| matchers.TestingLibraryMatchers<unknown, Promise<void>> {
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()
118 changes: 118 additions & 0 deletions types/__tests__/jest-globals/jest-globals-types.test.ts
Original file line number Diff line number Diff line change
@@ -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()
9 changes: 9 additions & 0 deletions types/__tests__/jest-globals/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"types": []
},
"include": ["*.ts"]
}
96 changes: 96 additions & 0 deletions types/__tests__/jest/jest-custom-expect-types.test.ts
Original file line number Diff line number Diff line change
@@ -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<unknown, void>
| matchers.TestingLibraryMatchers<unknown, Promise<void>> {
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()
Loading

0 comments on commit bdb34f1

Please sign in to comment.