Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,12 @@ expectTypeOf([1, 2, 3]).items.toBeNumber()
expectTypeOf([1, 2, 3]).items.not.toBeString()
```

You can also compare arrays directly:

```typescript
expectTypeOf<any[]>().not.toEqualTypeOf<number[]>()
```

Check that functions never return:

```typescript
Expand Down Expand Up @@ -420,6 +426,22 @@ class C {

expectTypeOf<typeof A>().toEqualTypeOf<typeof C>()
```

Known limitation: Intersection types can cause issues with `toEqualTypeOf`:

```typescript
// @ts-expect-error the following line doesn't compile, even though the types are arguably the same.
// See https://github.com/mmkal/expect-type/pull/21
expectTypeOf<{a: 1} & {b: 2}>().toEqualTypeOf<{a: 1; b: 2}>()
```

To workaround, you can use a mapped type:

```typescript
type Simplify<T> = {[K in keyof T]: T[K]}

expectTypeOf<Simplify<{a: 1} & {b: 2}>>().toEqualTypeOf<{a: 1; b: 2}>()
```
<!-- codegen:end -->

### Within test frameworks
Expand Down
114 changes: 71 additions & 43 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,17 @@ type ReadonlyEquivalent<X, Y> = Extends<
export type Extends<L, R> = IsNever<L> extends true ? IsNever<R> : [L] extends [R] ? true : false
export type StrictExtends<L, R> = Extends<DeepBrand<L>, DeepBrand<R>>

export type Equal<Left, Right> = And<[StrictExtends<Left, Right>, StrictExtends<Right, Left>]>
type StrictEqual<L, R> = (<T>() => T extends (L & T) | T ? true : false) extends <T>() => T extends (R & T) | T
? true
: false
? IsNever<L> extends IsNever<R>
? true
: false
: false

export type Equal<Left, Right, Branded = true> = Branded extends true
? And<[StrictExtends<Left, Right>, StrictExtends<Right, Left>]>
: StrictEqual<Left, Right>

export type Params<Actual> = Actual extends (...args: infer P) => any ? P : never
export type ConstructorParams<Actual> = Actual extends new (...args: infer P) => any
Expand All @@ -93,63 +103,78 @@ export type ConstructorParams<Actual> = Actual extends new (...args: infer P) =>
: P
: never

type MismatchArgs<B extends boolean, C extends boolean> = Eq<B, C> extends true ? [] : [never]

export interface ExpectTypeOf<Actual, B extends boolean> {
toBeAny: (...MISMATCH: MismatchArgs<IsAny<Actual>, B>) => true
toBeUnknown: (...MISMATCH: MismatchArgs<IsUnknown<Actual>, B>) => true
toBeNever: (...MISMATCH: MismatchArgs<IsNever<Actual>, B>) => true
toBeFunction: (...MISMATCH: MismatchArgs<Extends<Actual, (...args: any[]) => any>, B>) => true
toBeObject: (...MISMATCH: MismatchArgs<Extends<Actual, object>, B>) => true
toBeArray: (...MISMATCH: MismatchArgs<Extends<Actual, any[]>, B>) => true
toBeNumber: (...MISMATCH: MismatchArgs<Extends<Actual, number>, B>) => true
toBeString: (...MISMATCH: MismatchArgs<Extends<Actual, string>, B>) => true
toBeBoolean: (...MISMATCH: MismatchArgs<Extends<Actual, boolean>, B>) => true
toBeVoid: (...MISMATCH: MismatchArgs<Extends<Actual, void>, B>) => true
toBeSymbol: (...MISMATCH: MismatchArgs<Extends<Actual, symbol>, B>) => true
toBeNull: (...MISMATCH: MismatchArgs<Extends<Actual, null>, B>) => true
toBeUndefined: (...MISMATCH: MismatchArgs<Extends<Actual, undefined>, B>) => true
toBeNullable: (...MISMATCH: MismatchArgs<Not<Equal<Actual, NonNullable<Actual>>>, B>) => true
type MismatchArgs<ActualResult extends boolean, ExpectedResult extends boolean> = Eq<
ActualResult,
ExpectedResult
> extends true
? []
: [never]

export interface ExpectTypeOfOptions {
positive: boolean
branded: boolean
}
export interface ExpectTypeOf<Actual, Options extends ExpectTypeOfOptions> {
toBeAny: (...MISMATCH: MismatchArgs<IsAny<Actual>, Options['positive']>) => true
toBeUnknown: (...MISMATCH: MismatchArgs<IsUnknown<Actual>, Options['positive']>) => true
toBeNever: (...MISMATCH: MismatchArgs<IsNever<Actual>, Options['positive']>) => true
toBeFunction: (...MISMATCH: MismatchArgs<Extends<Actual, (...args: any[]) => any>, Options['positive']>) => true
toBeObject: (...MISMATCH: MismatchArgs<Extends<Actual, object>, Options['positive']>) => true
toBeArray: (...MISMATCH: MismatchArgs<Extends<Actual, any[]>, Options['positive']>) => true
toBeNumber: (...MISMATCH: MismatchArgs<Extends<Actual, number>, Options['positive']>) => true
toBeString: (...MISMATCH: MismatchArgs<Extends<Actual, string>, Options['positive']>) => true
toBeBoolean: (...MISMATCH: MismatchArgs<Extends<Actual, boolean>, Options['positive']>) => true
toBeVoid: (...MISMATCH: MismatchArgs<Extends<Actual, void>, Options['positive']>) => true
toBeSymbol: (...MISMATCH: MismatchArgs<Extends<Actual, symbol>, Options['positive']>) => true
toBeNull: (...MISMATCH: MismatchArgs<Extends<Actual, null>, Options['positive']>) => true
toBeUndefined: (...MISMATCH: MismatchArgs<Extends<Actual, undefined>, Options['positive']>) => true
toBeNullable: (
...MISMATCH: MismatchArgs<Not<Equal<Actual, NonNullable<Actual>, Options['branded']>>, Options['positive']>
) => true
toMatchTypeOf: {
<Expected>(...MISMATCH: MismatchArgs<Extends<Actual, Expected>, B>): true
<Expected>(expected: Expected, ...MISMATCH: MismatchArgs<Extends<Actual, Expected>, B>): true
<Expected>(...MISMATCH: MismatchArgs<Extends<Actual, Expected>, Options['positive']>): true
<Expected>(expected: Expected, ...MISMATCH: MismatchArgs<Extends<Actual, Expected>, Options['positive']>): true
}
toEqualTypeOf: {
<Expected>(...MISMATCH: MismatchArgs<Equal<Actual, Expected>, B>): true
<Expected>(expected: Expected, ...MISMATCH: MismatchArgs<Equal<Actual, Expected>, B>): true
<Expected>(...MISMATCH: MismatchArgs<Equal<Actual, Expected, Options['branded']>, Options['positive']>): true
<Expected>(
expected: Expected,
...MISMATCH: MismatchArgs<Equal<Actual, Expected, Options['branded']>, Options['positive']>
): true
}
toBeCallableWith: B extends true ? (...args: Params<Actual>) => true : never
toBeConstructibleWith: B extends true ? (...args: ConstructorParams<Actual>) => true : never
toBeCallableWith: Options['positive'] extends true ? (...args: Params<Actual>) => true : never
toBeConstructibleWith: Options['positive'] extends true ? (...args: ConstructorParams<Actual>) => true : never
toHaveProperty: <K extends string>(
key: K,
...MISMATCH: MismatchArgs<Extends<K, keyof Actual>, B>
) => K extends keyof Actual ? ExpectTypeOf<Actual[K], B> : true
extract: <V>(v?: V) => ExpectTypeOf<Extract<Actual, V>, B>
exclude: <V>(v?: V) => ExpectTypeOf<Exclude<Actual, V>, B>
parameter: <K extends keyof Params<Actual>>(number: K) => ExpectTypeOf<Params<Actual>[K], B>
parameters: ExpectTypeOf<Params<Actual>, B>
constructorParameters: ExpectTypeOf<ConstructorParams<Actual>, B>
thisParameter: ExpectTypeOf<ThisParameterType<Actual>, B>
instance: Actual extends new (...args: any[]) => infer I ? ExpectTypeOf<I, B> : never
returns: Actual extends (...args: any[]) => infer R ? ExpectTypeOf<R, B> : never
resolves: Actual extends PromiseLike<infer R> ? ExpectTypeOf<R, B> : never
items: Actual extends ArrayLike<infer R> ? ExpectTypeOf<R, B> : never
guards: Actual extends (v: any, ...args: any[]) => v is infer T ? ExpectTypeOf<T, B> : never
...MISMATCH: MismatchArgs<Extends<K, keyof Actual>, Options['positive']>
) => K extends keyof Actual ? ExpectTypeOf<Actual[K], Options> : true
extract: <V>(v?: V) => ExpectTypeOf<Extract<Actual, V>, Options>
exclude: <V>(v?: V) => ExpectTypeOf<Exclude<Actual, V>, Options>
parameter: <K extends keyof Params<Actual>>(number: K) => ExpectTypeOf<Params<Actual>[K], Options>
parameters: ExpectTypeOf<Params<Actual>, Options>
constructorParameters: ExpectTypeOf<ConstructorParams<Actual>, Options>
thisParameter: ExpectTypeOf<ThisParameterType<Actual>, Options>
instance: Actual extends new (...args: any[]) => infer I ? ExpectTypeOf<I, Options> : never
returns: Actual extends (...args: any[]) => infer R ? ExpectTypeOf<R, Options> : never
resolves: Actual extends PromiseLike<infer R> ? ExpectTypeOf<R, Options> : never
items: Actual extends ArrayLike<infer R> ? ExpectTypeOf<R, Options> : never
guards: Actual extends (v: any, ...args: any[]) => v is infer T ? ExpectTypeOf<T, Options> : never
asserts: Actual extends (v: any, ...args: any[]) => asserts v is infer T
? // Guard methods `(v: any) => asserts v is T` does not actually defines a return type. Thus, any function taking 1 argument matches the signature before.
// In case the inferred assertion type `R` could not be determined (so, `unknown`), consider the function as a non-guard, and return a `never` type.
// See https://github.com/microsoft/TypeScript/issues/34636
unknown extends T
? never
: ExpectTypeOf<T, B>
: ExpectTypeOf<T, Options>
: never
not: Omit<ExpectTypeOf<Actual, Not<B>>, 'not'>
branded: Omit<ExpectTypeOf<Actual, {positive: Options['positive']; branded: true}>, 'branded'>
not: Omit<ExpectTypeOf<Actual, {positive: Not<Options['positive']>; branded: Options['branded']}>, 'not'>
}
const fn: any = () => true

export type _ExpectTypeOf = {
<Actual>(actual: Actual): ExpectTypeOf<Actual, true>
<Actual>(): ExpectTypeOf<Actual, true>
<Actual>(actual: Actual): ExpectTypeOf<Actual, {positive: true; branded: false}>
<Actual>(): ExpectTypeOf<Actual, {positive: true; branded: false}>
}

/**
Expand All @@ -174,7 +199,9 @@ export type _ExpectTypeOf = {
* @description
* See the [full docs](https://npmjs.com/package/expect-type#documentation) for lots more examples.
*/
export const expectTypeOf: _ExpectTypeOf = <Actual>(_actual?: Actual): ExpectTypeOf<Actual, true> => {
export const expectTypeOf: _ExpectTypeOf = <Actual>(
_actual?: Actual,
): ExpectTypeOf<Actual, {positive: true; branded: false}> => {
const nonFunctionProperties = [
'parameters',
'returns',
Expand All @@ -186,6 +213,7 @@ export const expectTypeOf: _ExpectTypeOf = <Actual>(_actual?: Actual): ExpectTyp
'instance',
'guards',
'asserts',
'branded',
] as const
type Keys = keyof ExpectTypeOf<any, any>

Expand Down Expand Up @@ -220,5 +248,5 @@ export const expectTypeOf: _ExpectTypeOf = <Actual>(_actual?: Actual): ExpectTyp
const getterProperties: readonly Keys[] = nonFunctionProperties
getterProperties.forEach((prop: Keys) => Object.defineProperty(obj, prop, {get: () => expectTypeOf({})}))

return obj as ExpectTypeOf<Actual, true>
return obj as ExpectTypeOf<Actual, {positive: true; branded: false}>
}
Loading