Skip to content

Releases: mmkal/expect-type

v1.3.0

08 Dec 13:05

Choose a tag to compare

What's Changed

  • Update TypeScript to 5.9 and test against it by @charpeni in #166
  • Improve type error for failures on types with optional props by @mmkal in #160

Full Changelog: v1.2.2...v1.3.0

v1.2.2

06 Jul 00:27

Choose a tag to compare


v1.2.1...v1.2.2

v1.2.1

31 Mar 15:44

Choose a tag to compare

  • docs: update deprecated usage examples 0c5a05d

v1.2.0...v1.2.1

v1.2.0

28 Feb 18:44

Choose a tag to compare

What's Changed

  • .map fn by @mmkal in #98
  • toMatchObjectType + toExtend - replacements for toMatchTypeOf by @mmkal in #126

toMatchTypeOf is now deprecated. There are no plans to remove it any time soon, so it's not critical to immediately remove usages, but if you want to avoid squigglies in IDEs complaining about deprecations, here's what you should do:

If you have an assertion like this:

expectTypeOf(foo).toMatchTypeOf<Abc>()

There are a few options for upgrading it. The easiest is toExtend which is identical to the behaviour of toMatchTypeOf:

expectTypeOf(foo).toExtend<Abc>()

This will work in all cases. But, there is now a stricter option that will work in many cases and be slightly more likely to catch things like readonly properties matching:

expectTypeOf(foo).toMatchObjectType<Abc>()

But, as the name suggests, this will only work on plain object types, it will fail for union types, and some other complex types.


If you have code like this:

expectTypeOf(foo).toMatchTypeOf(bar)

You'll need to use typeof because toExtend and toMatchObjectType do not accept arguments

expectTypeOf(foo).toExtend<typeof bar>()
expectTypeOf(foo).toMatchObjectType<typeof bar>()

Full Changelog: v1.1.0...v1.2.0

v1.1.0

09 Oct 19:03

Choose a tag to compare

What's Changed

Full Changelog: v1.0.0...v1.1.0

v1.0.0

03 Oct 18:34

Choose a tag to compare

v1! 🎉🎉🎉

X (formerly Twitter) Follow

After many years being commitment-phobic, expect-type is now in v1.

This release does not add any user facing features on top of v0.20.0 or v1.0.0-rc.0. It's just "making it official". For anyone new to the project, or coming here from vitest or viteconf (👋 ), the usage docs from the readme are pasted below.

For anyone on an old-ish v0 version, here are links to the non-trivial changes that have gone in since v0.15.0:

  • v0.20.0: Function overloads support (proper support, beyond the default typescript functionality which eliminates all but one overloads by default)
  • v0.19.0: Beefed up JSDocs thanks to @aryaemami59
  • v0.18.0: .pick and .omit thanks to @aryaemami59
  • v0.17.0: massively improved error messages, so (in most cases) when an assertion fails you can see what's wrong, not just that something is wrong
  • v0.16.0: default to internal typescript implementation of type-identicalness. Introduce the .branded helper for the old behaviour. Also support function this parameters - thank to @trevorade and @papb

Full usage docs below, for newbies (head to the readme to keep up to date):

docs from readme

Installation and usage

npm install expect-type --save-dev
import {expectTypeOf} from 'expect-type'

Documentation

The expectTypeOf method takes a single argument or a generic type parameter. Neither it nor the functions chained off its return value have any meaningful runtime behaviour. The assertions you write will be compile-time errors if they don't hold true.

Features

Check an object's type with .toEqualTypeOf:

expectTypeOf({a: 1}).toEqualTypeOf<{a: number}>()

.toEqualTypeOf can check that two concrete objects have equivalent types (note: when these assertions fail, the error messages can be less informative vs the generic type argument syntax above - see error messages docs):

expectTypeOf({a: 1}).toEqualTypeOf({a: 1})

.toEqualTypeOf succeeds for objects with different values, but the same type:

expectTypeOf({a: 1}).toEqualTypeOf({a: 2})

.toEqualTypeOf fails on excess properties:

// @ts-expect-error
expectTypeOf({a: 1, b: 1}).toEqualTypeOf<{a: number}>()

To allow for extra properties, use .toMatchTypeOf. This is roughly equivalent to an extends constraint in a function type argument.:

expectTypeOf({a: 1, b: 1}).toMatchTypeOf<{a: number}>()

.toEqualTypeOf and .toMatchTypeOf both fail on missing properties:

// @ts-expect-error
expectTypeOf({a: 1}).toEqualTypeOf<{a: number; b: number}>()
// @ts-expect-error
expectTypeOf({a: 1}).toMatchTypeOf<{a: number; b: number}>()

Another example of the difference between .toMatchTypeOf and .toEqualTypeOf, using generics. .toMatchTypeOf can be used for "is-a" relationships:

type Fruit = {type: 'Fruit'; edible: boolean}
type Apple = {type: 'Fruit'; name: 'Apple'; edible: true}

expectTypeOf<Apple>().toMatchTypeOf<Fruit>()

// @ts-expect-error
expectTypeOf<Fruit>().toMatchTypeOf<Apple>()

// @ts-expect-error
expectTypeOf<Apple>().toEqualTypeOf<Fruit>()

Assertions can be inverted with .not:

expectTypeOf({a: 1}).not.toMatchTypeOf({b: 1})

.not can be easier than relying on // @ts-expect-error:

type Fruit = {type: 'Fruit'; edible: boolean}
type Apple = {type: 'Fruit'; name: 'Apple'; edible: true}

expectTypeOf<Apple>().toMatchTypeOf<Fruit>()

expectTypeOf<Fruit>().not.toMatchTypeOf<Apple>()
expectTypeOf<Apple>().not.toEqualTypeOf<Fruit>()

Catch any/unknown/never types:

expectTypeOf<unknown>().toBeUnknown()
expectTypeOf<any>().toBeAny()
expectTypeOf<never>().toBeNever()

// @ts-expect-error
expectTypeOf<never>().toBeNumber()

.toEqualTypeOf distinguishes between deeply-nested any and unknown properties:

expectTypeOf<{deeply: {nested: any}}>().not.toEqualTypeOf<{deeply: {nested: unknown}}>()

You can test for basic JavaScript types:

expectTypeOf(() => 1).toBeFunction()
expectTypeOf({}).toBeObject()
expectTypeOf([]).toBeArray()
expectTypeOf('').toBeString()
expectTypeOf(1).toBeNumber()
expectTypeOf(true).toBeBoolean()
expectTypeOf(() => {}).returns.toBeVoid()
expectTypeOf(Promise.resolve(123)).resolves.toBeNumber()
expectTypeOf(Symbol(1)).toBeSymbol()

.toBe... methods allow for types that extend the expected type:

expectTypeOf<number>().toBeNumber()
expectTypeOf<1>().toBeNumber()

expectTypeOf<any[]>().toBeArray()
expectTypeOf<number[]>().toBeArray()

expectTypeOf<string>().toBeString()
expectTypeOf<'foo'>().toBeString()

expectTypeOf<boolean>().toBeBoolean()
expectTypeOf<true>().toBeBoolean()

.toBe... methods protect against any:

const goodIntParser = (s: string) => Number.parseInt(s, 10)
const badIntParser = (s: string) => JSON.parse(s) // uh-oh - works at runtime if the input is a number, but return 'any'

expectTypeOf(goodIntParser).returns.toBeNumber()
// @ts-expect-error - if you write a test like this, `.toBeNumber()` will let you know your implementation returns `any`.
expectTypeOf(badIntParser).returns.toBeNumber()

Nullable types:

expectTypeOf(undefined).toBeUndefined()
expectTypeOf(undefined).toBeNullable()
expectTypeOf(undefined).not.toBeNull()

expectTypeOf(null).toBeNull()
expectTypeOf(null).toBeNullable()
expectTypeOf(null).not.toBeUndefined()

expectTypeOf<1 | undefined>().toBeNullable()
expectTypeOf<1 | null>().toBeNullable()
expectTypeOf<1 | undefined | null>().toBeNullable()

More .not examples:

expectTypeOf(1).not.toBeUnknown()
expectTypeOf(1).not.toBeAny()
expectTypeOf(1).not.toBeNever()
expectTypeOf(1).not.toBeNull()
expectTypeOf(1).not.toBeUndefined()
expectTypeOf(1).not.toBeNullable()

Detect assignability of unioned types:

expectTypeOf<number>().toMatchTypeOf<string | number>()
expectTypeOf<string | number>().not.toMatchTypeOf<number>()

Use .extract and .exclude to narrow down complex union types:

type ResponsiveProp<T> = T | T[] | {xs?: T; sm?: T; md?: T}
const getResponsiveProp = <T>(_props: T): ResponsiveProp<T> => ({})
type CSSProperties = {margin?: string; padding?: string}

const cssProperties: CSSProperties = {margin: '1px', padding: '2px'}

expectTypeOf(getResponsiveProp(cssProperties))
  .exclude<unknown[]>()
  .exclude<{xs?: unknown}>()
  .toEqualTypeOf<CSSProperties>()

expectTypeOf(getResponsiveProp(cssProperties))
  .extract<unknown[]>()
  .toEqualTypeOf<CSSProperties[]>()

expectTypeOf(getResponsiveProp(cssProperties))
  .extract<{xs?: any}>()
  .toEqualTypeOf<{xs?: CSSProperties; sm?: CSSProperties; md?: CSSProperties}>()

expectTypeOf<ResponsiveProp<number>>().exclude<number | number[]>().toHaveProperty('sm')
expectTypeOf<ResponsiveProp<number>>().exclude<number | number[]>().not.toHaveProperty('xxl')

.extract and .exclude return never if no types remain after exclusion:

type Person = {name: string; age: number}
type Customer = Person & {customerId: string}
type Employee = Person & {employeeId: string}

expectTypeOf<Customer | Employee>().extract<{foo: string}>().toBeNever()
expectTypeOf<Customer | Employee>().exclude<{name: string}>().toBeNever()

Use .pick to pick a set of properties from an object:

type Person = {name: string; age: number}

expectTypeOf<Person>().pick<'name'>().toEqualTypeOf<{name: string}>()

Use .omit to remove a set of properties from an object:

type Person = {name: string; age: number}

expectTypeOf<Person>().omit<'name'>().toEqualTypeOf<{age: number}>()

Make assertions about object properties:

const obj = {a: 1, b: ''}

// check that properties exist (or don't) with `.toHaveProperty`
expectTypeOf(obj).toHaveProperty('a')
expectTypeOf(obj).not.toHaveProperty('c')

// check types of properties
expectTypeOf(obj).toHaveProperty('a').toBeNumber()
expectTypeOf(obj).toHaveProperty('b').toBeString()
expectTypeOf(obj).toHaveProperty('a').not.toBeString()

.toEqualTypeOf can be used to distinguish between functions:

type NoParam = () => void
type HasParam = (s: string) => void

expectTypeOf<NoParam>().not.toEqualTypeOf<HasParam>()

But often it's preferable to use .parameters or .returns for more specific function assertions:

type NoParam = () => void
type HasParam = (s: string) => void

expectTypeOf<NoParam>().parameters.toEqualTypeOf<[]>()
expectTypeOf<NoParam>().returns.toBeVoid()

expectTypeOf<HasParam>().parameters.toEqualTypeOf<[string]>()
expectTypeOf<HasParam>().returns.toBeVoid()

Up to ten overloads will produce union types for .parameters and .returns:

type Factorize = {
  (input: number): number[]
  (input: bigint): bigint[]
}

expectTypeOf<Factorize>().parameters.toEqualTypeOf<[number] | [bigint]>()
expectTypeOf<Factorize>().returns.toEqualTypeOf<number[] | bigint[]>()

expectTypeOf<Factorize>().parameter(0).toEqualTypeOf<number | bigint>()

Note that these aren't exactly like TypeScr...

Read more

v1.0.0-rc.0

10 Sep 01:50

Choose a tag to compare

v1.0.0-rc.0 Pre-release
Pre-release

1.0.0 release candidate

No changes other than dev dependency updates since v0.20.0: https://github.com/mmkal/expect-type/releases/tag/v0.20.0

The intent is to publish a 1.0.0 as-is, since this is used in vitest already.

v0.20.0

20 Aug 16:20

Choose a tag to compare

Breaking changes

  • improve overloads support, attempt 2 by @mmkal in #83

This change updates how overloaded functions are treated. Now, .parameters gives you a union of the parameter-tuples that a function can take. For example, given the following type:

type Factorize = {
  (input: number): number[]
  (input: bigint): bigint[]
}

Behvaiour before:

expectTypeOf<Factorize>().parameters.toEqualTypeOf<[bigint]>()

Behaviour now:

expectTypeOf<Factorize>().parameters.toEqualTypeOf<[number] | [bigint]>()

There were similar changes for .returns, .parameter(...), and .toBeCallableWith. Also, overloaded functions are now differentiated properly when using .branded.toEqualTypeOf (this was a bug that it seems nobody found).

See #83 for more details or look at the updated docs (including a new section called "Overloaded functions", which has more info on how this behaviour differs for TypeScript versions before 5.3).

What's Changed

Full Changelog: v0.19.0...v0.20.0

v0.20.0-0

13 Aug 18:18

Choose a tag to compare

v0.20.0-0 Pre-release
Pre-release

Breaking changes

  • improve overloads support, attempt 2 by @mmkal in #83

This change updates how overloaded functions are treated. Now, .parameters gives you a union of the parameter-tuples that a function can take. For example, given the following type:

type Factorize = {
  (input: number): number[]
  (input: bigint): bigint[]
}

Behvaiour before:

expectTypeOf<Factorize>().parameters.toEqualTypeOf<[bigint]>()

Behaviour now:

expectTypeOf<Factorize>().parameters.toEqualTypeOf<[number] | [bigint]>()

There were similar changes for .returns, .parameter(...), and .toBeCallableWith. Also, overloaded functions are now differentiated properly when using .branded.toEqualTypeOf (this was a bug that it seems nobody found).

See #83 for more details or look at the updated docs (including a new section called "Overloaded functions", which has more info on how this behaviour differs for TypeScript versions before 5.3.

What's Changed

New Contributors

Full Changelog: v0.17.3...v0.20.0-0

0.19.0

21 Mar 21:00

Choose a tag to compare

What's Changed

Full Changelog: 0.18.0...0.19.0