Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

"print" types to compare them + better error messages #27

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
50005ee
toEqualTypeOf generic constraint
mmkal Oct 23, 2022
ce87fc4
also toMatchTypeOf although message is slightly misleading
mmkal Oct 23, 2022
c688e72
Merge branch 'main' into dx-generics
mmkal Oct 23, 2022
e779861
format typescript diagnostics in error test
mmkal Oct 23, 2022
a851221
Merge branch 'main' into dx-generics
mmkal Oct 23, 2022
1a660a9
Update error snapshots
mmkal Oct 23, 2022
a0a1171
Merge branch 'main' into dx-generics
mmkal Oct 23, 2022
aa08372
get rid of ...MISMATCH??
mmkal Oct 23, 2022
14e9ef2
handle never/any edge cases
mmkal Oct 23, 2022
bfc0e0b
Merge branch 'main' into dx-generics
mmkal Oct 23, 2022
da5bafd
Update snapshot
mmkal Oct 23, 2022
7324e5d
Move exhaustive any/unknown/never tetsts out of usage.test.ts
mmkal Oct 24, 2022
1504ad1
rm temp thing
mmkal Oct 24, 2022
368e9f0
update snapshot
mmkal Oct 24, 2022
9ca8f44
rm unused type
mmkal Oct 24, 2022
6677de3
ignore line numbers
mmkal Apr 8, 2023
f28b88f
map leaf types
mmkal Apr 9, 2023
516e0be
Fallback on ...MISMATCH
mmkal Apr 9, 2023
3ae12fc
Rename
mmkal Apr 9, 2023
984bb31
deprecate inferred-from-value expectations
mmkal Apr 9, 2023
f12f366
old-usage.test.ts for deprecated method
mmkal Apr 9, 2023
68e7fcf
beef up literals test
mmkal Apr 10, 2023
4d47b0b
type record - potential alternative to DeepBrand
mmkal Apr 10, 2023
b112be9
optionals problem
mmkal Apr 10, 2023
2d01a54
nice
mmkal Apr 10, 2023
0451916
optional obj doesn't work
mmkal Apr 10, 2023
fbf345b
no
mmkal Apr 10, 2023
a1bed2a
maybe
mmkal Apr 10, 2023
cacdb3d
probably
mmkal Apr 10, 2023
4307934
seems functional
mmkal Apr 11, 2023
a4fca78
cleanup
mmkal Apr 11, 2023
8a57a4a
[string, string] pairs
mmkal Apr 11, 2023
75b923b
reorder tests
mmkal Apr 11, 2023
baaeabd
reorder
mmkal Apr 11, 2023
4a5c986
PrintProps
mmkal Apr 11, 2023
538ed29
avoid infinite recursion
mmkal Apr 11, 2023
f9a909c
handle void
mmkal Apr 11, 2023
7367470
docs
mmkal Apr 11, 2023
95e79e0
Merge remote-tracking branch 'origin/main' into type-record
mmkal Apr 11, 2023
68ad64d
update snapshots
mmkal Apr 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
109 changes: 56 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,56 +56,53 @@ The `expectTypeOf` method takes a single argument, or a generic parameter. Neith
### Features

<!-- codegen:start {preset: markdownFromTests, source: test/usage.test.ts} -->
Check an object's type with `.toEqualTypeOf`:
Check an object's type with `.toBeIdenticalTo`:

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

`.toEqualTypeOf` can check that two concrete objects have equivalent types:
`.toBeIdenticalTo` fails on extra properties:

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

`.toEqualTypeOf` succeeds for objects with different values, but the same type:
To allow for extra properties, use `.toExtend`. This checks that an object "matches" a type. This is similar to jest's `.toMatchObject`:

```typescript
expectTypeOf({a: 1}).toEqualTypeOf({a: 2})
expectTypeOf({a: 1, b: 1}).toExtend<{a: number}>()
```

`.toEqualTypeOf` fails on extra properties:
`.toBeIdenticalTo` and `.toExtend` both fail on missing properties:

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

To allow for extra properties, use `.toMatchTypeOf`. This checks that an object "matches" a type. This is similar to jest's `.toMatchObject`:

```typescript
expectTypeOf({a: 1, b: 1}).toMatchTypeOf({a: 1})
expectTypeOf({a: 1}).toBeIdenticalTo<{a: number; b: number}>()
// @ts-expect-error
expectTypeOf({a: 1}).toExtend<{a: number; b: number}>()
```

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

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

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

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

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

Assertions can be inverted with `.not`:

```typescript
expectTypeOf({a: 1}).not.toMatchTypeOf({b: 1})
expectTypeOf({a: 1}).not.toExtend<{b: number}>()
```

`.not` can be easier than relying on `// @ts-expect-error`:
Expand All @@ -114,10 +111,10 @@ expectTypeOf({a: 1}).not.toMatchTypeOf({b: 1})
type Fruit = {type: 'Fruit'; edible: boolean}
type Apple = {type: 'Fruit'; name: 'Apple'; edible: true}

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

expectTypeOf<Fruit>().not.toMatchTypeOf<Apple>()
expectTypeOf<Apple>().not.toEqualTypeOf<Fruit>()
expectTypeOf<Fruit>().not.toExtend<Apple>()
expectTypeOf<Apple>().not.toBeIdenticalTo<Fruit>()
```

Catch any/unknown/never types:
Expand All @@ -131,10 +128,10 @@ expectTypeOf<never>().toBeNever()
expectTypeOf<never>().toBeNumber()
```

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

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

Test for basic javascript types:
Expand Down Expand Up @@ -181,8 +178,8 @@ expectTypeOf(1).not.toBeNullable()
Detect assignability of unioned types:

```typescript
expectTypeOf<number>().toMatchTypeOf<string | number>()
expectTypeOf<string | number>().not.toMatchTypeOf<number>()
expectTypeOf<number>().toExtend<string | number>()
expectTypeOf<string | number>().not.toExtend<number>()
```

Use `.extract` and `.exclude` to narrow down complex union types:
Expand All @@ -197,15 +194,15 @@ const cssProperties: CSSProperties = {margin: '1px', padding: '2px'}
expectTypeOf(getResponsiveProp(cssProperties))
.exclude<unknown[]>()
.exclude<{xs?: unknown}>()
.toEqualTypeOf<CSSProperties>()
.toBeIdenticalTo<CSSProperties>()

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

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

expectTypeOf<ResponsiveProp<number>>().exclude<number | number[]>().toHaveProperty('sm')
expectTypeOf<ResponsiveProp<number>>().exclude<number | number[]>().not.toHaveProperty('xxl')
Expand Down Expand Up @@ -237,13 +234,13 @@ expectTypeOf(obj).toHaveProperty('b').toBeString()
expectTypeOf(obj).toHaveProperty('a').not.toBeString()
```

`.toEqualTypeOf` can be used to distinguish between functions:
`.toBeIdenticalTo` can be used to distinguish between functions:

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

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

But often it's preferable to use `.parameters` or `.returns` for more specific function assertions:
Expand All @@ -252,10 +249,10 @@ But often it's preferable to use `.parameters` or `.returns` for more specific f
type NoParam = () => void
type HasParam = (s: string) => void

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

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

Expand All @@ -269,15 +266,14 @@ expectTypeOf(f).toBeFunction()
expectTypeOf(f).toBeCallableWith(1)
expectTypeOf(f).not.toBeAny()
expectTypeOf(f).returns.not.toBeAny()
expectTypeOf(f).returns.toEqualTypeOf([1, 2])
expectTypeOf(f).returns.toEqualTypeOf([1, 2, 3])
expectTypeOf(f).parameter(0).not.toEqualTypeOf('1')
expectTypeOf(f).parameter(0).toEqualTypeOf(1)
expectTypeOf(f).returns.toBeIdenticalTo<number[]>()
expectTypeOf(f).parameter(0).not.toBeIdenticalTo<string>()
expectTypeOf(f).parameter(0).toBeIdenticalTo<number>()
expectTypeOf(1).parameter(0).toBeNever()

const twoArgFunc = (a: number, b: string) => ({a, b})

expectTypeOf(twoArgFunc).parameters.toEqualTypeOf<[number, string]>()
expectTypeOf(twoArgFunc).parameters.toBeIdenticalTo<[number, string]>()
```

You can also check type guards & type assertions:
Expand All @@ -303,7 +299,7 @@ expectTypeOf(Date).toBeConstructibleWith(0)
expectTypeOf(Date).toBeConstructibleWith(new Date())
expectTypeOf(Date).toBeConstructibleWith()

expectTypeOf(Date).constructorParameters.toEqualTypeOf<[] | [string | number | Date]>()
expectTypeOf(Date).constructorParameters.toBeIdenticalTo<[] | [string | number | Date]>()
```

Check function `this` parameters:
Expand All @@ -313,7 +309,7 @@ function greet(this: {name: string}, message: string) {
return `Hello ${this.name}, here's your message: ${message}`
}

expectTypeOf(greet).thisParameter.toEqualTypeOf<{name: string}>()
expectTypeOf(greet).thisParameter.toBeIdenticalTo<{name: string}>()
```

Distinguish between functions with different `this` parameters:
Expand All @@ -327,7 +323,7 @@ function greetCasual(this: {name: string}, message: string) {
return `Hi ${this.name}, here's your message: ${message}`
}

expectTypeOf(greetFormal).not.toEqualTypeOf(greetCasual)
expectTypeOf(greetFormal).not.toBeIdenticalTo<typeof greetCasual>()
```

Class instance types:
Expand Down Expand Up @@ -364,17 +360,17 @@ expectTypeOf(thrower).returns.toBeNever()
Generics can be used rather than references:

```typescript
expectTypeOf<{a: string}>().not.toEqualTypeOf<{a: number}>()
expectTypeOf<{a: string}>().not.toBeIdenticalTo<{a: number}>()
```

Distinguish between missing/null/optional properties:

```typescript
expectTypeOf<{a?: number}>().not.toEqualTypeOf<{}>()
expectTypeOf<{a?: number}>().not.toEqualTypeOf<{a: number}>()
expectTypeOf<{a?: number}>().not.toEqualTypeOf<{a: number | undefined}>()
expectTypeOf<{a?: number | null}>().not.toEqualTypeOf<{a: number | null}>()
expectTypeOf<{a: {b?: number}}>().not.toEqualTypeOf<{a: {}}>()
expectTypeOf<{a?: number}>().not.toBeIdenticalTo<{}>()
expectTypeOf<{a?: number}>().not.toBeIdenticalTo<{a: number}>()
expectTypeOf<{a?: number}>().not.toBeIdenticalTo<{a: number | undefined}>()
expectTypeOf<{a?: number | null}>().not.toBeIdenticalTo<{a: number | null}>()
expectTypeOf<{a: {b?: number}}>().not.toBeIdenticalTo<{a: {}}>()
```

Detect the difference between regular and readonly properties:
Expand All @@ -383,14 +379,14 @@ Detect the difference between regular and readonly properties:
type A1 = {readonly a: string; b: string}
type E1 = {a: string; b: string}

expectTypeOf<A1>().toMatchTypeOf<E1>()
expectTypeOf<A1>().not.toEqualTypeOf<E1>()
expectTypeOf<A1>().toExtend<E1>()
expectTypeOf<A1>().not.toBeIdenticalTo<E1>()

type A2 = {a: string; b: {readonly c: string}}
type E2 = {a: string; b: {c: string}}

expectTypeOf<A2>().toMatchTypeOf<E2>()
expectTypeOf<A2>().not.toEqualTypeOf<E2>()
expectTypeOf<A2>().toExtend<E2>()
expectTypeOf<A2>().not.toBeIdenticalTo<E2>()
```

Distinguish between classes with different constructors:
Expand All @@ -409,7 +405,7 @@ class B {
}
}

expectTypeOf<typeof A>().not.toEqualTypeOf<typeof B>()
expectTypeOf<typeof A>().not.toBeIdenticalTo<typeof B>()

class C {
value: number
Expand All @@ -418,7 +414,14 @@ class C {
}
}

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

Functions with extra properties can be checked too:

```typescript
const augmented = Object.assign((a: number, b: number) => a + b, {foo: 'bar'})
expectTypeOf(augmented).toBeIdenticalTo<((a: number, b: number) => number) & {foo: string}>()
```
<!-- codegen:end -->

Expand Down