diff --git a/README.md b/README.md index f2bb1b16..66c47ca8 100644 --- a/README.md +++ b/README.md @@ -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().not.toEqualTypeOf() +``` + Check that functions never return: ```typescript @@ -420,6 +426,22 @@ class C { expectTypeOf().toEqualTypeOf() ``` + +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 = {[K in keyof T]: T[K]} + +expectTypeOf>().toEqualTypeOf<{a: 1; b: 2}>() +``` ### Within test frameworks diff --git a/src/index.ts b/src/index.ts index f936b4e5..ded80201 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,7 +84,17 @@ type ReadonlyEquivalent = Extends< export type Extends = IsNever extends true ? IsNever : [L] extends [R] ? true : false export type StrictExtends = Extends, DeepBrand> -export type Equal = And<[StrictExtends, StrictExtends]> +type StrictEqual = (() => T extends (L & T) | T ? true : false) extends () => T extends (R & T) | T + ? true + : false + ? IsNever extends IsNever + ? true + : false + : false + +export type Equal = Branded extends true + ? And<[StrictExtends, StrictExtends]> + : StrictEqual export type Params = Actual extends (...args: infer P) => any ? P : never export type ConstructorParams = Actual extends new (...args: infer P) => any @@ -93,63 +103,78 @@ export type ConstructorParams = Actual extends new (...args: infer P) => : P : never -type MismatchArgs = Eq extends true ? [] : [never] - -export interface ExpectTypeOf { - toBeAny: (...MISMATCH: MismatchArgs, B>) => true - toBeUnknown: (...MISMATCH: MismatchArgs, B>) => true - toBeNever: (...MISMATCH: MismatchArgs, B>) => true - toBeFunction: (...MISMATCH: MismatchArgs any>, B>) => true - toBeObject: (...MISMATCH: MismatchArgs, B>) => true - toBeArray: (...MISMATCH: MismatchArgs, B>) => true - toBeNumber: (...MISMATCH: MismatchArgs, B>) => true - toBeString: (...MISMATCH: MismatchArgs, B>) => true - toBeBoolean: (...MISMATCH: MismatchArgs, B>) => true - toBeVoid: (...MISMATCH: MismatchArgs, B>) => true - toBeSymbol: (...MISMATCH: MismatchArgs, B>) => true - toBeNull: (...MISMATCH: MismatchArgs, B>) => true - toBeUndefined: (...MISMATCH: MismatchArgs, B>) => true - toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true +type MismatchArgs = Eq< + ActualResult, + ExpectedResult +> extends true + ? [] + : [never] + +export interface ExpectTypeOfOptions { + positive: boolean + branded: boolean +} +export interface ExpectTypeOf { + toBeAny: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeUnknown: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeNever: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeFunction: (...MISMATCH: MismatchArgs any>, Options['positive']>) => true + toBeObject: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeArray: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeNumber: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeString: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeBoolean: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeVoid: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeSymbol: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeNull: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeUndefined: (...MISMATCH: MismatchArgs, Options['positive']>) => true + toBeNullable: ( + ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> + ) => true toMatchTypeOf: { - (...MISMATCH: MismatchArgs, B>): true - (expected: Expected, ...MISMATCH: MismatchArgs, B>): true + (...MISMATCH: MismatchArgs, Options['positive']>): true + (expected: Expected, ...MISMATCH: MismatchArgs, Options['positive']>): true } toEqualTypeOf: { - (...MISMATCH: MismatchArgs, B>): true - (expected: Expected, ...MISMATCH: MismatchArgs, B>): true + (...MISMATCH: MismatchArgs, Options['positive']>): true + ( + expected: Expected, + ...MISMATCH: MismatchArgs, Options['positive']> + ): true } - toBeCallableWith: B extends true ? (...args: Params) => true : never - toBeConstructibleWith: B extends true ? (...args: ConstructorParams) => true : never + toBeCallableWith: Options['positive'] extends true ? (...args: Params) => true : never + toBeConstructibleWith: Options['positive'] extends true ? (...args: ConstructorParams) => true : never toHaveProperty: ( key: K, - ...MISMATCH: MismatchArgs, B> - ) => K extends keyof Actual ? ExpectTypeOf : true - extract: (v?: V) => ExpectTypeOf, B> - exclude: (v?: V) => ExpectTypeOf, B> - parameter: >(number: K) => ExpectTypeOf[K], B> - parameters: ExpectTypeOf, B> - constructorParameters: ExpectTypeOf, B> - thisParameter: ExpectTypeOf, B> - instance: Actual extends new (...args: any[]) => infer I ? ExpectTypeOf : never - returns: Actual extends (...args: any[]) => infer R ? ExpectTypeOf : never - resolves: Actual extends PromiseLike ? ExpectTypeOf : never - items: Actual extends ArrayLike ? ExpectTypeOf : never - guards: Actual extends (v: any, ...args: any[]) => v is infer T ? ExpectTypeOf : never + ...MISMATCH: MismatchArgs, Options['positive']> + ) => K extends keyof Actual ? ExpectTypeOf : true + extract: (v?: V) => ExpectTypeOf, Options> + exclude: (v?: V) => ExpectTypeOf, Options> + parameter: >(number: K) => ExpectTypeOf[K], Options> + parameters: ExpectTypeOf, Options> + constructorParameters: ExpectTypeOf, Options> + thisParameter: ExpectTypeOf, Options> + instance: Actual extends new (...args: any[]) => infer I ? ExpectTypeOf : never + returns: Actual extends (...args: any[]) => infer R ? ExpectTypeOf : never + resolves: Actual extends PromiseLike ? ExpectTypeOf : never + items: Actual extends ArrayLike ? ExpectTypeOf : never + guards: Actual extends (v: any, ...args: any[]) => v is infer T ? ExpectTypeOf : 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 + : ExpectTypeOf : never - not: Omit>, 'not'> + branded: Omit, 'branded'> + not: Omit; branded: Options['branded']}>, 'not'> } const fn: any = () => true export type _ExpectTypeOf = { - (actual: Actual): ExpectTypeOf - (): ExpectTypeOf + (actual: Actual): ExpectTypeOf + (): ExpectTypeOf } /** @@ -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): ExpectTypeOf => { +export const expectTypeOf: _ExpectTypeOf = ( + _actual?: Actual, +): ExpectTypeOf => { const nonFunctionProperties = [ 'parameters', 'returns', @@ -186,6 +213,7 @@ export const expectTypeOf: _ExpectTypeOf = (_actual?: Actual): ExpectTyp 'instance', 'guards', 'asserts', + 'branded', ] as const type Keys = keyof ExpectTypeOf @@ -220,5 +248,5 @@ export const expectTypeOf: _ExpectTypeOf = (_actual?: Actual): ExpectTyp const getterProperties: readonly Keys[] = nonFunctionProperties getterProperties.forEach((prop: Keys) => Object.defineProperty(obj, prop, {get: () => expectTypeOf({})})) - return obj as ExpectTypeOf + return obj as ExpectTypeOf } diff --git a/test/errors.test.ts b/test/errors.test.ts index d2cc07eb..755b5cfa 100644 --- a/test/errors.test.ts +++ b/test/errors.test.ts @@ -18,9 +18,9 @@ test('toEqualTypeOf<...>() error message', async () => { 3 expectTypeOf({a: 1}).toEqualTypeOf<{a: string}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. " `) @@ -43,9 +43,9 @@ test('toMatchTypeOf<...>() error message', async () => { 3 expectTypeOf({a: 1}).toMatchTypeOf<{a: string}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:114:16 - 114 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:135:16 + 135 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. " `) @@ -58,9 +58,9 @@ test('toMatchTypeOf(...) error message', async () => { 3 expectTypeOf({a: 1}).toMatchTypeOf({a: 'one'}) ~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:115:36 - 115 (expected: Expected, ...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:136:36 + 136 (expected: Expected, ...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. " `) @@ -86,234 +86,234 @@ test('usage test', () => { 21 expectTypeOf({a: 1, b: 1}).toEqualTypeOf<{a: number}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:35:25 - error TS2554: Expected 1 arguments, but got 0. 35 expectTypeOf().toMatchTypeOf() ~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:114:16 - 114 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:135:16 + 135 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:38:25 - error TS2554: Expected 1 arguments, but got 0. 38 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:42:24 - error TS2554: Expected 2 arguments, but got 1. 42 expectTypeOf({a: 1}).toMatchTypeOf({b: 1}) ~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:115:36 - 115 (expected: Expected, ...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:136:36 + 136 (expected: Expected, ...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:51:25 - error TS2554: Expected 1 arguments, but got 0. 51 expectTypeOf().toMatchTypeOf() ~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:114:16 - 114 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:135:16 + 135 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:52:25 - error TS2554: Expected 1 arguments, but got 0. 52 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:61:25 - error TS2554: Expected 1 arguments, but got 0. 61 expectTypeOf().toBeNumber() ~~~~~~~~~~~~ - src/index.ts:105:16 - 105 toBeNumber: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:124:16 + 124 toBeNumber: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:65:43 - error TS2554: Expected 1 arguments, but got 0. 65 expectTypeOf<{deeply: {nested: any}}>().toEqualTypeOf<{deeply: {nested: unknown}}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:83:27 - error TS2554: Expected 1 arguments, but got 0. 83 expectTypeOf(undefined).toBeNullable() ~~~~~~~~~~~~~~ - src/index.ts:112:18 - 112 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:132:5 + 132 ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:87:22 - error TS2554: Expected 1 arguments, but got 0. 87 expectTypeOf(null).toBeNullable() ~~~~~~~~~~~~~~ - src/index.ts:112:18 - 112 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:132:5 + 132 ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:90:33 - error TS2554: Expected 1 arguments, but got 0. 90 expectTypeOf<1 | undefined>().toBeNullable() ~~~~~~~~~~~~~~ - src/index.ts:112:18 - 112 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:132:5 + 132 ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:91:28 - error TS2554: Expected 1 arguments, but got 0. 91 expectTypeOf<1 | null>().toBeNullable() ~~~~~~~~~~~~~~ - src/index.ts:112:18 - 112 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:132:5 + 132 ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:92:40 - error TS2554: Expected 1 arguments, but got 0. 92 expectTypeOf<1 | undefined | null>().toBeNullable() ~~~~~~~~~~~~~~ - src/index.ts:112:18 - 112 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:132:5 + 132 ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:96:19 - error TS2554: Expected 1 arguments, but got 0. 96 expectTypeOf(1).toBeUnknown() ~~~~~~~~~~~~~ - src/index.ts:100:17 - 100 toBeUnknown: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:119:17 + 119 toBeUnknown: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:97:19 - error TS2554: Expected 1 arguments, but got 0. 97 expectTypeOf(1).toBeAny() ~~~~~~~~~ - src/index.ts:99:13 - 99 toBeAny: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:118:13 + 118 toBeAny: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:98:19 - error TS2554: Expected 1 arguments, but got 0. 98 expectTypeOf(1).toBeNever() ~~~~~~~~~~~ - src/index.ts:101:15 - 101 toBeNever: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:120:15 + 120 toBeNever: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:99:19 - error TS2554: Expected 1 arguments, but got 0. 99 expectTypeOf(1).toBeNull() ~~~~~~~~~~ - src/index.ts:110:14 - 110 toBeNull: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:129:14 + 129 toBeNull: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:100:19 - error TS2554: Expected 1 arguments, but got 0. 100 expectTypeOf(1).toBeUndefined() ~~~~~~~~~~~~~~~ - src/index.ts:111:19 - 111 toBeUndefined: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:130:19 + 130 toBeUndefined: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:101:19 - error TS2554: Expected 1 arguments, but got 0. 101 expectTypeOf(1).toBeNullable() ~~~~~~~~~~~~~~ - src/index.ts:112:18 - 112 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:132:5 + 132 ...MISMATCH: MismatchArgs, Options['branded']>>, Options['positive']> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:106:35 - error TS2554: Expected 1 arguments, but got 0. 106 expectTypeOf().toMatchTypeOf() ~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:114:16 - 114 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:135:16 + 135 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:130:71 - error TS2554: Expected 2 arguments, but got 1. 130 expectTypeOf>().exclude().toHaveProperty('xxl') ~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:125:5 - 125 ...MISMATCH: MismatchArgs, B> - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:149:5 + 149 ...MISMATCH: MismatchArgs, Options['positive']> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:147:21 - error TS2554: Expected 2 arguments, but got 1. 147 expectTypeOf(obj).toHaveProperty('c') ~~~~~~~~~~~~~~~~~~~ - src/index.ts:125:5 - 125 ...MISMATCH: MismatchArgs, B> - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:149:5 + 149 ...MISMATCH: MismatchArgs, Options['positive']> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:152:41 - error TS2554: Expected 1 arguments, but got 0. 152 expectTypeOf(obj).toHaveProperty('a').toBeString() ~~~~~~~~~~~~ - src/index.ts:106:16 - 106 toBeString: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:125:16 + 125 toBeString: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:159:27 - error TS2554: Expected 1 arguments, but got 0. 159 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:179:19 - error TS2554: Expected 1 arguments, but got 0. 179 expectTypeOf(f).toBeAny() ~~~~~~~~~ - src/index.ts:99:13 - 99 toBeAny: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:118:13 + 118 toBeAny: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:180:27 - error TS2554: Expected 1 arguments, but got 0. 180 expectTypeOf(f).returns.toBeAny() ~~~~~~~~~ - src/index.ts:99:13 - 99 toBeAny: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:118:13 + 118 toBeAny: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:183:46 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. @@ -328,72 +328,108 @@ test('usage test', () => { 246 expectTypeOf([1, 2, 3]).items.toBeString() ~~~~~~~~~~~~ - src/index.ts:106:16 - 106 toBeString: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:125:16 + 125 toBeString: (...MISMATCH: MismatchArgs, Options['positive']>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:258:31 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:250:25 - error TS2554: Expected 1 arguments, but got 0. - 258 expectTypeOf<{a: string}>().toEqualTypeOf<{a: number}>() + 250 expectTypeOf().toEqualTypeOf() + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. + test/usage.test.ts:262:31 - error TS2554: Expected 1 arguments, but got 0. + + 262 expectTypeOf<{a: string}>().toEqualTypeOf<{a: number}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. + test/usage.test.ts:266:32 - error TS2554: Expected 1 arguments, but got 0. + + 266 expectTypeOf<{a?: number}>().toEqualTypeOf<{}>() + ~~~~~~~~~~~~~~~~~~~ + + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:263:32 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:267:32 - error TS2554: Expected 1 arguments, but got 0. - 263 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number}>() + 267 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:264:32 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:268:32 - error TS2554: Expected 1 arguments, but got 0. - 264 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number | undefined}>() + 268 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number | undefined}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:265:39 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:269:39 - error TS2554: Expected 1 arguments, but got 0. - 265 expectTypeOf<{a?: number | null}>().toEqualTypeOf<{a: number | null}>() + 269 expectTypeOf<{a?: number | null}>().toEqualTypeOf<{a: number | null}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:274:22 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:270:37 - error TS2554: Expected 1 arguments, but got 0. - 274 expectTypeOf().toEqualTypeOf() + 270 expectTypeOf<{a: {b?: number}}>().toEqualTypeOf<{a: {}}>() + ~~~~~~~~~~~~~~~~~~~~~~~~ + + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. + test/usage.test.ts:278:22 - error TS2554: Expected 1 arguments, but got 0. + + 278 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:280:22 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:284:22 - error TS2554: Expected 1 arguments, but got 0. - 280 expectTypeOf().toEqualTypeOf() + 284 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:297:28 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:301:28 - error TS2554: Expected 1 arguments, but got 0. - 297 expectTypeOf().toEqualTypeOf() + 301 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. + test/usage.test.ts:316:35 - error TS2554: Expected 1 arguments, but got 0. + + 316 expectTypeOf<{a: 1} & {b: 2}>().toEqualTypeOf<{a: 1; b: 2}>() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + src/index.ts:139:16 + 139 (...MISMATCH: MismatchArgs, Options['positive']>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. " `) diff --git a/test/types.test.ts b/test/types.test.ts index 5c02214e..2e57ed4f 100644 --- a/test/types.test.ts +++ b/test/types.test.ts @@ -70,9 +70,11 @@ test(`never types don't sneak by`, () => { expectTypeOf().toMatchTypeOf<{foo: string}>() }) -test('intersections work properly', () => { +test('intersections do not currently work properly', () => { + // @ts-expect-error limitation of new implementation https://github.com/mmkal/expect-type/pull/21 expectTypeOf<{a: 1} & {b: 2}>().toEqualTypeOf<{a: 1; b: 2}>() expectTypeOf<{a: 1} & {b: 2}>().toMatchTypeOf<{a: 1; b: 2}>() + // @ts-expect-error limitation of new implementation https://github.com/mmkal/expect-type/pull/21 expectTypeOf<{a: 1; b: 2}>().toEqualTypeOf<{a: 1} & {b: 2}>() expectTypeOf<{a: 1; b: 2}>().toMatchTypeOf<{a: 1} & {b: 2}>() }) @@ -200,11 +202,454 @@ test(`undefined isn't removed from unions`, () => { expectTypeOf().toMatchTypeOf() }) +test('Distinguish between functions whose return types differ by readonly prop', () => { + type ObjWithReadonlyProp = {readonly x: number} + type ObjWithoutReadonlyProp = {x: number} + + function original(o: ObjWithReadonlyProp): ObjWithReadonlyProp { + return o + } + + function same(o: ObjWithReadonlyProp): ObjWithReadonlyProp { + return o + } + + function different(o: ObjWithoutReadonlyProp): ObjWithoutReadonlyProp { + return o + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf(original).branded.toEqualTypeOf(original) + expectTypeOf().toEqualTypeOf() + expectTypeOf(different).toEqualTypeOf(different) + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf(original).not.toEqualTypeOf(original) + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf(different).not.toEqualTypeOf(different) + + // Same shape + expectTypeOf().toEqualTypeOf() + expectTypeOf(original).toEqualTypeOf(same) + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf(original).not.toEqualTypeOf(same) + + // Different presence of readonly prop + expectTypeOf().not.toEqualTypeOf() + expectTypeOf(original).not.toEqualTypeOf(different) + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf(original).toEqualTypeOf(different) +}) + +test('Distinguish between classes with only private properties', () => { + class Original { + // eslint-disable-next-line mmkal/@typescript-eslint/class-literal-property-style + private readonly prop = 1 + } + + class Different { + // eslint-disable-next-line mmkal/@typescript-eslint/class-literal-property-style + private readonly prop = 1 + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf(Original).toEqualTypeOf(Original) + expectTypeOf().toEqualTypeOf() + expectTypeOf(Different).toEqualTypeOf(Different) + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf(Original).not.toEqualTypeOf(Original) + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf(Different).not.toEqualTypeOf(Different) + + // Different classes + expectTypeOf().not.toEqualTypeOf() + expectTypeOf(Original).not.toEqualTypeOf(Different) + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf(Original).toEqualTypeOf(Different) +}) + +test('Distinguish between types with generics used in type assertion', () => { + interface Guard { + // eslint-disable-next-line mmkal/@typescript-eslint/prefer-function-type + (arg: unknown): arg is T + } + + // Self-identity + expectTypeOf>().toEqualTypeOf>() + // @ts-expect-error + expectTypeOf>().not.toEqualTypeOf>() + + // Different return types + expectTypeOf>().not.toEqualTypeOf>() + // @ts-expect-error + expectTypeOf>().toEqualTypeOf>() +}) + +test('Distinguish between functions with generics vs unknown', () => { + function funcWithGenerics(p1: T, p2: T): T { + return p1 || p2 + } + + function funcWithUnknown(p1: unknown, p2: unknown): unknown { + return p1 || p2 + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf(funcWithGenerics).toEqualTypeOf(funcWithGenerics) + expectTypeOf().toEqualTypeOf() + expectTypeOf(funcWithUnknown).toEqualTypeOf(funcWithUnknown) + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf(funcWithGenerics).not.toEqualTypeOf(funcWithGenerics) + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf(funcWithUnknown).not.toEqualTypeOf(funcWithUnknown) + + // Generic vs unknown with otherwise same shape + expectTypeOf().not.toEqualTypeOf() + expectTypeOf(funcWithGenerics).not.toEqualTypeOf(funcWithUnknown) + // @ts-expect-error + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf(funcWithGenerics).toEqualTypeOf(funcWithUnknown) +}) + +interface BaseFunc { + // eslint-disable-next-line mmkal/@typescript-eslint/prefer-function-type + (str: string): number +} + +test('Distinguish between functions with readonly properties', () => { + interface Original extends BaseFunc { + readonly prop: string + } + + interface Same extends BaseFunc { + readonly prop: string + } + + interface Different extends BaseFunc { + prop: string + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Same shape + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Only one readonly otherwise same shape + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() +}) + +test('Distinguish between functions with optional properties', () => { + interface Original extends BaseFunc { + prop?: number + } + + interface Same extends BaseFunc { + prop?: number + } + + interface Different extends BaseFunc { + prop: number | undefined + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Same shape + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Only one optional otherwise same shape + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() +}) + +test('Distinguish between functions with properties of different types', () => { + interface Original extends BaseFunc { + prop: number + } + + interface Same extends BaseFunc { + prop: number + } + + interface Different extends BaseFunc { + prop: string + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Same shape + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Only one optional otherwise same shape + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() +}) + +interface BaseConstructor { + // eslint-disable-next-line mmkal/@typescript-eslint/prefer-function-type + new (str: string): {someProp: number} +} + +test('Distinguish between constructors with readonly properties', () => { + interface Original extends BaseConstructor { + readonly prop: string + } + + interface Same extends BaseConstructor { + readonly prop: string + } + + interface Different extends BaseConstructor { + prop: string + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Same shape + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Only one readonly otherwise same shape + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() +}) + +test('Distinguish between constructors with optional properties', () => { + interface Original extends BaseConstructor { + prop?: number + } + + interface Same extends BaseConstructor { + prop?: number + } + + interface Different extends BaseConstructor { + prop: number | undefined + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Same shape + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Only one optional otherwise same shape + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() +}) + +test('Distinguish between constructors with properties of different types', () => { + interface Original extends BaseConstructor { + prop: number + } + + interface Same extends BaseConstructor { + prop: number + } + + interface Different extends BaseConstructor { + prop: string + } + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Same shape + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Only one optional otherwise same shape + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() +}) + +test('Distinguish between tuples with differing item type', () => { + type Original = [{prop: number}] + type Same = [{prop: number}] + type Different = [{readonly prop: number}] + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Same shape + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // One item type property readonly otherwise same sape + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() +}) + +test('Distinguish between array with properties', () => { + type Original = number[] & {readonly prop: number} + type Same = number[] & {readonly prop: number} + type Different = number[] & {prop: number} + + // Self-identity + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // Same shape + expectTypeOf().toEqualTypeOf() + // @ts-expect-error + expectTypeOf().not.toEqualTypeOf() + + // One item type property readonly otherwise same sape + expectTypeOf().not.toEqualTypeOf() + // @ts-expect-error + expectTypeOf().toEqualTypeOf() +}) + +test('Distinguish between different types that are OR`d together', () => { + expectTypeOf<{foo: number} | {bar: string}>().toEqualTypeOf<{foo: number} | {bar: string}>() + // @ts-expect-error + expectTypeOf<{foo: number} | {bar: string}>().not.toEqualTypeOf<{foo: number} | {bar: string}>() + + expectTypeOf<{foo: number} | {bar: string}>().not.toEqualTypeOf<{foo: number}>() + // @ts-expect-error + expectTypeOf<{foo: number} | {bar: string}>().toEqualTypeOf<{foo: number}>() +}) + +test('Distinguish between identical types that are OR`d together', () => { + expectTypeOf<{foo: number} | {foo: number}>().toEqualTypeOf<{foo: number} | {foo: number}>() + // Note: The `| T` in `Equal` in index.ts makes this work. + expectTypeOf<{foo: number} | {foo: number}>().toEqualTypeOf<{foo: number}>() + // @ts-expect-error + expectTypeOf<{foo: number} | {foo: number}>().not.toEqualTypeOf<{foo: number} | {foo: number}>() + // @ts-expect-error + expectTypeOf<{foo: number} | {foo: number}>().not.toEqualTypeOf<{foo: number}>() +}) + +test('Distinguish between different types that are AND`d together', () => { + // Identity + expectTypeOf<{foo: number} & {bar: string}>().toEqualTypeOf<{foo: number} & {bar: string}>() + // @ts-expect-error + expectTypeOf<{foo: number} & {bar: string}>().not.toEqualTypeOf<{foo: number} & {bar: string}>() + + // Two types intersect to an equivalent non-intersected type + // This is broken at the moment. See the next test + // expectTypeOf<{foo: number} & {bar: string}>().toEqualTypeOf<{foo: number; bar: string}>() + // expectTypeOf<{foo: number} & {bar: string}>().not.toEqualTypeOf<{foo: number; bar: string}>() +}) + +test('Works arounds tsc bug not handling intersected types for this form of equivalence', () => { + // @ts-expect-error This is the bug. + expectTypeOf<{foo: number} & {bar: string}>().toEqualTypeOf<{foo: number; bar: string}>() + // This should \@ts-expect-error but does not. + expectTypeOf<{foo: number} & {bar: string}>().not.toEqualTypeOf<{foo: number; bar: string}>() + + const one: {foo: number} & {bar: string} = {foo: 1, bar: 'a'} + const two: {foo: number; bar: string} = {foo: 1, bar: 'a'} + // @ts-expect-error It also repros with variables and their inferred types + expectTypeOf(one).toEqualTypeOf(two) + // This should \@ts-expect-error but does not. + expectTypeOf(one).not.toEqualTypeOf(two) + + // The workaround is the new optional .branded modifier. + expectTypeOf<{foo: number} & {bar: string}>().branded.toEqualTypeOf<{foo: number; bar: string}>() + expectTypeOf(one).branded.toEqualTypeOf(two) + // @ts-expect-error + expectTypeOf<{foo: number} & {bar: string}>().branded.not.toEqualTypeOf<{foo: number; bar: string}>() + // @ts-expect-error + expectTypeOf(one).branded.not.toEqualTypeOf(two) +}) + +test('Distinguish between identical types that are AND`d together', () => { + expectTypeOf<{foo: number} & {foo: number}>().toEqualTypeOf<{foo: number} & {foo: number}>() + // Note: The `& T` in `Equal` in index.ts makes this work. + expectTypeOf<{foo: number} & {foo: number}>().toEqualTypeOf<{foo: number}>() + // @ts-expect-error + expectTypeOf<{foo: number} & {foo: number}>().not.toEqualTypeOf<{foo: number} & {foo: number}>() + // @ts-expect-error + expectTypeOf<{foo: number} & {foo: number}>().not.toEqualTypeOf<{foo: number}>() +}) + test('limitations', () => { // these *shouldn't* fail, but kept here to document missing behaviours. Once fixed, remove the expect-error comments to make sure they can't regress // @ts-expect-error typescript can't handle the truth: https://github.com/mmkal/expect-type/issues/5 https://github.com/microsoft/TypeScript/issues/50670 expectTypeOf () => () => void, () => () => () => string>>().toEqualTypeOf() - // @ts-expect-error this is just a bug - augmented functions slip through the net https://github.com/mmkal/expect-type/issues/26 expectTypeOf<(() => 1) & {x: 1}>().not.toEqualTypeOf<() => 1>() }) diff --git a/test/usage.test.ts b/test/usage.test.ts index e1c0e6fe..950fd303 100644 --- a/test/usage.test.ts +++ b/test/usage.test.ts @@ -246,6 +246,10 @@ test('Array items can be checked with `.items`', () => { expectTypeOf([1, 2, 3]).items.not.toBeString() }) +test('You can also compare arrays directly', () => { + expectTypeOf().not.toEqualTypeOf() +}) + test('Check that functions never return', () => { const thrower = () => { throw new Error('oh no') @@ -305,3 +309,15 @@ test('Distinguish between classes with different constructors', () => { expectTypeOf().toEqualTypeOf() }) + +test('Known limitation: Intersection types can cause issues with `toEqualTypeOf`', () => { + // @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}>() +}) + +test('To workaround, you can use a mapped type', () => { + type Simplify = {[K in keyof T]: T[K]} + + expectTypeOf>().toEqualTypeOf<{a: 1; b: 2}>() +})