diff --git a/src/index.ts b/src/index.ts index 2e4532b..3d4610d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,79 +12,15 @@ export type IsAny = [T] extends [Secret] ? Not> : false export type IsUnknown = [unknown] extends [T] ? Not> : false export type IsNeverOrAny = Or<[IsNever, IsAny]> -/** - * Recursively walk a type and replace it with a branded type related to the original. This is useful for - * equality-checking stricter than `A extends B ? B extends A ? true : false : false`, because it detects - * the difference between a few edge-case types that vanilla typescript doesn't by default: - * - `any` vs `unknown` - * - `{ readonly a: string }` vs `{ a: string }` - * - `{ a?: string }` vs `{ a: string | undefined }` - */ -export type DeepBrand = IsNever extends true - ? {type: 'never'} - : IsAny extends true - ? {type: 'any'} - : IsUnknown extends true - ? {type: 'unknown'} - : T extends string | number | boolean | symbol | bigint | null | undefined | void - ? { - type: 'primitive' - value: T - } - : T extends new (...args: any[]) => any - ? { - type: 'constructor' - params: ConstructorParams - instance: DeepBrand any>>> - } - : T extends (...args: infer P) => infer R // avoid functions with different params/return values matching - ? { - type: 'function' - params: DeepBrand

- return: DeepBrand - this: DeepBrand> - } - : T extends any[] - ? { - type: 'array' - items: {[K in keyof T]: T[K]} - } - : { - type: 'object' - properties: {[K in keyof T]: DeepBrand} - readonly: ReadonlyKeys - required: RequiredKeys - optional: OptionalKeys - constructorParams: DeepBrand> - } - -export type RequiredKeys = Extract< - { - [K in keyof T]-?: {} extends Pick ? never : K - }[keyof T], - keyof T -> -export type OptionalKeys = Exclude> - -// adapted from some answers to https://github.com/type-challenges/type-challenges/issues?q=label%3A5+label%3Aanswer -// prettier-ignore -export type ReadonlyKeys = Extract<{ - [K in keyof T]-?: ReadonlyEquivalent< - {[_K in K]: T[K]}, - {-readonly [_K in K]: T[K]} - > extends true ? never : K; -}[keyof T], keyof T>; - -// prettier-ignore -type ReadonlyEquivalent = Extends< - (() => T extends X ? true : false), - (() => T extends Y ? true : false) -> - export type Extends = IsNever extends true ? IsNever : [L] extends [R] ? true : false -export type StrictExtends = Extends, DeepBrand> -export type Equal = And<[StrictExtends, StrictExtends]> +export type Simplify = {[K in keyof T]: T[K]} +type MaybeSimplify = S extends true ? Simplify : T + +export type Equal = + (() => T extends (L & T) | T ? true : false) extends + (() => T extends (R & T) | T ? true : false) ? + IsNever extends IsNever ? true : false : false export type Params = Actual extends (...args: infer P) => any ? P : never export type ConstructorParams = Actual extends new (...args: infer P) => any @@ -95,7 +31,7 @@ export type ConstructorParams = Actual extends new (...args: infer P) => type MismatchArgs = Eq extends true ? [] : [never] -export interface ExpectTypeOf { +export interface ExpectTypeOf { toBeAny: (...MISMATCH: MismatchArgs, B>) => true toBeUnknown: (...MISMATCH: MismatchArgs, B>) => true toBeNever: (...MISMATCH: MismatchArgs, B>) => true @@ -111,39 +47,40 @@ export interface ExpectTypeOf { toBeUndefined: (...MISMATCH: MismatchArgs, B>) => true toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true toMatchTypeOf: { - (...MISMATCH: MismatchArgs, B>): true - (expected: Expected, ...MISMATCH: MismatchArgs, B>): true + (...MISMATCH: MismatchArgs>, B>): true + (expected: Expected, ...MISMATCH: MismatchArgs>, B>): true } toEqualTypeOf: { - (...MISMATCH: MismatchArgs, B>): true - (expected: Expected, ...MISMATCH: MismatchArgs, B>): true + (...MISMATCH: MismatchArgs>, B>): true + (expected: Expected, ...MISMATCH: MismatchArgs>, B>): true } toBeCallableWith: B extends true ? (...args: Params) => true : never toBeConstructibleWith: B 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 + ) => K extends keyof Actual ? ExpectTypeOf : true + extract: (v?: V) => ExpectTypeOf, B, S> + exclude: (v?: V) => ExpectTypeOf, B, S> + parameter: >(number: K) => ExpectTypeOf[K], B, S> + parameters: ExpectTypeOf, B, S> + constructorParameters: ExpectTypeOf, B, S> + thisParameter: ExpectTypeOf, B, S> + 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: ExpectTypeOf> + simplified: Omit, B, true>, 'simplified'> + not: Omit, S>, 'simplified'|'not'> } const fn: any = () => true @@ -186,6 +123,7 @@ export const expectTypeOf: _ExpectTypeOf = (_actual?: Actual): ExpectTyp 'instance', 'guards', 'asserts', + 'simplified', ] as const type Keys = keyof ExpectTypeOf diff --git a/test/errors.test.ts b/test/errors.test.ts index d2cc07e..98e5c76 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:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:50:16 + 50 (...MISMATCH: MismatchArgs>, B>): 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:51:36 + 51 (expected: Expected, ...MISMATCH: MismatchArgs>, B>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. " `) @@ -86,134 +86,134 @@ 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:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:50:16 + 50 (...MISMATCH: MismatchArgs>, B>): 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:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:51:36 + 51 (expected: Expected, ...MISMATCH: MismatchArgs>, B>): 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:50:16 + 50 (...MISMATCH: MismatchArgs>, B>): 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:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:41:16 + 41 toBeNumber: (...MISMATCH: MismatchArgs, B>) => 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:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:48:18 + 48 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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:48:18 + 48 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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:48:18 + 48 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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:48:18 + 48 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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:48:18 + 48 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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:36:17 + 36 toBeUnknown: (...MISMATCH: MismatchArgs, B>) => 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:35:13 + 35 toBeAny: (...MISMATCH: MismatchArgs, B>) => true ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. test/usage.test.ts:98:19 - error TS2554: Expected 1 arguments, but got 0. @@ -221,179 +221,197 @@ test('usage test', () => { 98 expectTypeOf(1).toBeNever() ~~~~~~~~~~~ - src/index.ts:101:15 - 101 toBeNever: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:37:15 + 37 toBeNever: (...MISMATCH: MismatchArgs, B>) => 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:46:14 + 46 toBeNull: (...MISMATCH: MismatchArgs, B>) => 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:47:19 + 47 toBeUndefined: (...MISMATCH: MismatchArgs, B>) => 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:48:18 + 48 toBeNullable: (...MISMATCH: MismatchArgs>>, B>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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:50:16 + 50 (...MISMATCH: MismatchArgs>, B>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:130:71 - error TS2554: Expected 2 arguments, but got 1. + test/usage.test.ts:128:71 - error TS2554: Expected 2 arguments, but got 1. - 130 expectTypeOf>().exclude().toHaveProperty('xxl') + 128 expectTypeOf>().exclude().toHaveProperty('xxl') ~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:125:5 - 125 ...MISMATCH: MismatchArgs, B> - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:61:5 + 61 ...MISMATCH: MismatchArgs, B> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:147:21 - error TS2554: Expected 2 arguments, but got 1. + test/usage.test.ts:145:21 - error TS2554: Expected 2 arguments, but got 1. - 147 expectTypeOf(obj).toHaveProperty('c') + 145 expectTypeOf(obj).toHaveProperty('c') ~~~~~~~~~~~~~~~~~~~ - src/index.ts:125:5 - 125 ...MISMATCH: MismatchArgs, B> - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:61:5 + 61 ...MISMATCH: MismatchArgs, B> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:152:41 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:150:41 - error TS2554: Expected 1 arguments, but got 0. - 152 expectTypeOf(obj).toHaveProperty('a').toBeString() + 150 expectTypeOf(obj).toHaveProperty('a').toBeString() ~~~~~~~~~~~~ - src/index.ts:106:16 - 106 toBeString: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:42:16 + 42 toBeString: (...MISMATCH: MismatchArgs, B>) => true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:159:27 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:157:27 - error TS2554: Expected 1 arguments, but got 0. - 159 expectTypeOf().toEqualTypeOf() + 157 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:179:19 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:177:19 - error TS2554: Expected 1 arguments, but got 0. - 179 expectTypeOf(f).toBeAny() + 177 expectTypeOf(f).toBeAny() ~~~~~~~~~ - src/index.ts:99:13 - 99 toBeAny: (...MISMATCH: MismatchArgs, B>) => true + src/index.ts:35:13 + 35 toBeAny: (...MISMATCH: MismatchArgs, B>) => true ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. - test/usage.test.ts:180:27 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:178:27 - error TS2554: Expected 1 arguments, but got 0. - 180 expectTypeOf(f).returns.toBeAny() + 178 expectTypeOf(f).returns.toBeAny() ~~~~~~~~~ - src/index.ts:99:13 - 99 toBeAny: (...MISMATCH: MismatchArgs, B>) => true + src/index.ts:35:13 + 35 toBeAny: (...MISMATCH: MismatchArgs, B>) => 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'. + test/usage.test.ts:181:46 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. - 183 expectTypeOf(f).parameter(0).toEqualTypeOf('1') + 181 expectTypeOf(f).parameter(0).toEqualTypeOf('1') ~~~ - test/usage.test.ts:231:43 - error TS2345: Argument of type '(this: { name: string; }, message: string) => string' is not assignable to parameter of type 'never'. + test/usage.test.ts:229:43 - error TS2345: Argument of type '(this: { name: string; }, message: string) => string' is not assignable to parameter of type 'never'. - 231 expectTypeOf(greetFormal).toEqualTypeOf(greetCasual) + 229 expectTypeOf(greetFormal).toEqualTypeOf(greetCasual) ~~~~~~~~~~~ - test/usage.test.ts:246:33 - error TS2554: Expected 1 arguments, but got 0. + test/usage.test.ts:244:33 - error TS2554: Expected 1 arguments, but got 0. - 246 expectTypeOf([1, 2, 3]).items.toBeString() + 244 expectTypeOf([1, 2, 3]).items.toBeString() ~~~~~~~~~~~~ - src/index.ts:106:16 - 106 toBeString: (...MISMATCH: MismatchArgs, B>) => true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:42:16 + 42 toBeString: (...MISMATCH: MismatchArgs, B>) => 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:256:31 - error TS2554: Expected 1 arguments, but got 0. - 258 expectTypeOf<{a: string}>().toEqualTypeOf<{a: number}>() + 256 expectTypeOf<{a: string}>().toEqualTypeOf<{a: number}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:260:32 - error TS2554: Expected 1 arguments, but got 0. - 263 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number}>() + 260 expectTypeOf<{a?: number}>().toEqualTypeOf<{}>() + ~~~~~~~~~~~~~~~~~~~ + + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. + test/usage.test.ts:261:32 - error TS2554: Expected 1 arguments, but got 0. + + 261 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:262:32 - error TS2554: Expected 1 arguments, but got 0. - 264 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number | undefined}>() + 262 expectTypeOf<{a?: number}>().toEqualTypeOf<{a: number | undefined}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:263:39 - error TS2554: Expected 1 arguments, but got 0. - 265 expectTypeOf<{a?: number | null}>().toEqualTypeOf<{a: number | null}>() + 263 expectTypeOf<{a?: number | null}>().toEqualTypeOf<{a: number | null}>() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Arguments for the rest parameter 'MISMATCH' were not provided. + test/usage.test.ts:264:37 - error TS2554: Expected 1 arguments, but got 0. + + 264 expectTypeOf<{a: {b?: number}}>().toEqualTypeOf<{a: {}}>() + ~~~~~~~~~~~~~~~~~~~~~~~~ + + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:272:22 - error TS2554: Expected 1 arguments, but got 0. - 274 expectTypeOf().toEqualTypeOf() + 272 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:278:22 - error TS2554: Expected 1 arguments, but got 0. - 280 expectTypeOf().toEqualTypeOf() + 278 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): 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:295:28 - error TS2554: Expected 1 arguments, but got 0. - 297 expectTypeOf().toEqualTypeOf() + 295 expectTypeOf().toEqualTypeOf() ~~~~~~~~~~~~~~~~~~~~~~~~~ - src/index.ts:118:16 - 118 (...MISMATCH: MismatchArgs, B>): true - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/index.ts:54:16 + 54 (...MISMATCH: MismatchArgs>, B>): true + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arguments for the rest parameter 'MISMATCH' were not provided. " `) diff --git a/test/types.test.ts b/test/types.test.ts index 9e69af1..de2ccab 100644 --- a/test/types.test.ts +++ b/test/types.test.ts @@ -174,8 +174,7 @@ test('parity with IsExact from conditional-type-checks', () => { test('Equal works with functions', () => { expectTypeOf void, () => string>>().toEqualTypeOf() expectTypeOf void, (s: string) => void>>().toEqualTypeOf() - // todo: workaround https://github.com/microsoft/TypeScript/issues/50670 - https://github.com/mmkal/expect-type/issues/5 - // expectTypeOf () => () => void, () => () => () => string>>().toEqualTypeOf() + expectTypeOf () => () => void, () => () => () => string>>().toEqualTypeOf() expectTypeOf () => () => void, () => (s: string) => () => void>>().toEqualTypeOf() }) @@ -189,3 +188,471 @@ test(`undefined isn't removed from unions`, () => { expectTypeOf().toEqualTypeOf() 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).simplified.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() +}) + +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() +}) + +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 .simplified modifier. + expectTypeOf<{foo: number} & {bar: string}>().simplified.toEqualTypeOf<{foo: number; bar: string}>() + expectTypeOf(one).simplified.toEqualTypeOf(two) + // @ts-expect-error + expectTypeOf<{foo: number} & {bar: string}>().simplified.not.toEqualTypeOf<{foo: number; bar: string}>() + // @ts-expect-error + expectTypeOf(one).simplified.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}>() +})