diff --git a/common/changes/expect-type/exclude-extract_pr-220.json b/common/changes/expect-type/exclude-extract_pr-220.json new file mode 100644 index 00000000..e0d16e46 --- /dev/null +++ b/common/changes/expect-type/exclude-extract_pr-220.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Add `.exclude` and `.extract` helpers (#220)", + "type": "minor", + "packageName": "expect-type" + } + ], + "packageName": "expect-type", + "email": "mmkal@users.noreply.github.com" +} \ No newline at end of file diff --git a/packages/expect-type/readme.md b/packages/expect-type/readme.md index 6da4d498..138ec3a6 100644 --- a/packages/expect-type/readme.md +++ b/packages/expect-type/readme.md @@ -179,6 +179,43 @@ expectTypeOf(1).not.toBeUndefined() expectTypeOf(1).not.toBeNullable() ``` +Use `.extract` and `.exclude` to narrow down complex union types: + +```typescript +type ResponsiveProp = T | T[] | {xs?: T; sm?: T; md?: T} +const getResponsiveProp = (props: T): ResponsiveProp => ({}) +type CSSProperties = {margin?: string; padding?: string} + +const cssProperties: CSSProperties = {margin: '1px', padding: '2px'} + +expectTypeOf(getResponsiveProp(cssProperties)) + .exclude() + .exclude<{xs?: unknown}>() + .toEqualTypeOf() + +expectTypeOf(getResponsiveProp(cssProperties)) + .extract() + .toEqualTypeOf() + +expectTypeOf(getResponsiveProp(cssProperties)) + .extract<{xs?: any}>() + .toEqualTypeOf<{xs?: CSSProperties; sm?: CSSProperties; md?: CSSProperties}>() + +expectTypeOf>().exclude().toHaveProperty('sm') +expectTypeOf>().exclude().not.toHaveProperty('xxl') +``` + +`.extract` and `.exclude` return never if no types remain after exclusion: + +```typescript +type Person = {name: string; age: number} +type Customer = Person & {customerId: string} +type Employee = Person & {employeeId: string} + +expectTypeOf().extract<{foo: string}>().toBeNever() +expectTypeOf().exclude<{name: string}>().toBeNever() +``` + Make assertions about object properties: ```typescript diff --git a/packages/expect-type/src/__tests__/index.test.ts b/packages/expect-type/src/__tests__/index.test.ts index 426d0f83..e6969947 100644 --- a/packages/expect-type/src/__tests__/index.test.ts +++ b/packages/expect-type/src/__tests__/index.test.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import {expectTypeOf} from '..' +/* eslint prettier/prettier: ["warn", { "singleQuote": true, "semi": false, "arrowParens": "avoid", "trailingComma": "es5", "bracketSpacing": false, "endOfLine": "auto", "printWidth": 100 }] */ + test("Check an object's type with `.toEqualTypeOf`", () => { expectTypeOf({a: 1}).toEqualTypeOf<{a: number}>() }) @@ -98,6 +100,39 @@ test('More `.not` examples', () => { expectTypeOf(1).not.toBeNullable() }) +test('Use `.extract` and `.exclude` to narrow down complex union types', () => { + type ResponsiveProp = T | T[] | {xs?: T; sm?: T; md?: T} + const getResponsiveProp = (props: T): ResponsiveProp => ({}) + type CSSProperties = {margin?: string; padding?: string} + + const cssProperties: CSSProperties = {margin: '1px', padding: '2px'} + + expectTypeOf(getResponsiveProp(cssProperties)) + .exclude() + .exclude<{xs?: unknown}>() + .toEqualTypeOf() + + expectTypeOf(getResponsiveProp(cssProperties)) + .extract() + .toEqualTypeOf() + + expectTypeOf(getResponsiveProp(cssProperties)) + .extract<{xs?: any}>() + .toEqualTypeOf<{xs?: CSSProperties; sm?: CSSProperties; md?: CSSProperties}>() + + expectTypeOf>().exclude().toHaveProperty('sm') + expectTypeOf>().exclude().not.toHaveProperty('xxl') +}) + +test('`.extract` and `.exclude` return never if no types remain after exclusion', () => { + type Person = {name: string; age: number} + type Customer = Person & {customerId: string} + type Employee = Person & {employeeId: string} + + expectTypeOf().extract<{foo: string}>().toBeNever() + expectTypeOf().exclude<{name: string}>().toBeNever() +}) + test('Make assertions about object properties', () => { const obj = {a: 1, b: ''} diff --git a/packages/expect-type/src/index.ts b/packages/expect-type/src/index.ts index 17df58c9..7a049fa3 100644 --- a/packages/expect-type/src/index.ts +++ b/packages/expect-type/src/index.ts @@ -104,6 +104,8 @@ export interface ExpectTypeOf { 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> @@ -174,6 +176,8 @@ export const expectTypeOf: _ExpectTypeOf = (actual?: Actual): ExpectType toEqualTypeOf: fn, toBeCallableWith: fn, toBeConstructibleWith: fn, + extract: expectTypeOf, + exclude: expectTypeOf, toHaveProperty: expectTypeOf, parameter: expectTypeOf, }