Releases: mmkal/expect-type
v1.3.0
v1.2.2
v1.2.1
v1.2.0
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
v1.0.0
v1! 🎉🎉🎉
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:
.pickand.omitthanks 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
.brandedhelper for the old behaviour. Also support functionthisparameters - 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...
v1.0.0-rc.0
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
Breaking changes
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
- Fix rendering issue in readme by @mrazauskas in #69
- Fix minor issues in docs by @aryaemami59 in #91
- create utils file by @mmkal in #93
- branding.ts and messages.ts by @mmkal in #95
- improve overloads support, attempt 2 by @mmkal in #83
- Extends: explain myself 1e37116
- Mark internal APIs with
@internalJSDoc tag (#104) 4c40b07 - Re-export everything in
overloads.tsfile (#107) 5ee0181 - JSDoc improvements (#100) 0bbeffa
Full Changelog: v0.19.0...v0.20.0
v0.20.0-0
Breaking changes
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
- Add
.pickand.omitby @aryaemami59 in #51 - Fix
.omit()to work similarly toOmitby @aryaemami59 in #54 - Add JSDocs to everything by @aryaemami59 in #56
- Test against different versions of TypeScript during CI by @aryaemami59 in #62
- Fix rendering issue in readme by @mrazauskas in #69
- Update LICENSE file to properly include copyright information by @trevorade in #72
- Test against TypeScript version 5.5 in CI by @aryaemami59 in #86
- Fix minor issues in docs by @aryaemami59 in #91
- create utils file by @mmkal in #93
- branding.ts and messages.ts by @mmkal in #95
- improve overloads support, attempt 2 by @mmkal in #83
New Contributors
- @renovate made their first contribution in #1
- @aryaemami59 made their first contribution in #51
- @mrazauskas made their first contribution in #69
- @github-actions made their first contribution in #88
Full Changelog: v0.17.3...v0.20.0-0
0.19.0
What's Changed
- Fix
.omit()to work similarly toOmitby @aryaemami59 in #54 - Add JSDocs to everything by @aryaemami59 in #56
- Remove
testimport inREADME.mdby @aryaemami59 in #65
Full Changelog: 0.18.0...0.19.0