From 75596139a98d4b4fb4e8a6bba03963fa0904ac23 Mon Sep 17 00:00:00 2001 From: Tao Cumplido Date: Mon, 6 Feb 2023 21:20:42 +0100 Subject: [PATCH] Infer thrown error from expectations --- test-types/import-in-cts/throws.cts | 21 ++++++++++++++++++--- test-types/module/throws.ts | 21 ++++++++++++++++++--- types/assertions.d.cts | 19 ++++++++++++------- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/test-types/import-in-cts/throws.cts b/test-types/import-in-cts/throws.cts index 126dd174b..088cce716 100644 --- a/test-types/import-in-cts/throws.cts +++ b/test-types/import-in-cts/throws.cts @@ -12,15 +12,30 @@ class CustomError extends Error { } test('throws', t => { - expectType(t.throws(() => {})); + const error1 = t.throws(() => {}); + expectType(error1); const error2: CustomError | undefined = t.throws(() => {}); expectType(error2); expectType(t.throws(() => {})); + const error3 = t.throws(() => {}, {instanceOf: CustomError}); + expectType(error3); + const error4 = t.throws(() => {}, {is: new CustomError()}); + expectType(error4); + const error5 = t.throws(() => {}, {instanceOf: CustomError, is: new CustomError()}); + expectType(error5); }); test('throwsAsync', async t => { - expectType(await t.throwsAsync(async () => {})); + const error1 = await t.throwsAsync(async () => {}); + expectType(error1); expectType(await t.throwsAsync(async () => {})); - expectType(await t.throwsAsync(Promise.reject())); + const error2 = await t.throwsAsync(Promise.reject()); + expectType(error2); expectType(await t.throwsAsync(Promise.reject())); + const error3 = await t.throwsAsync(async () => {}, {instanceOf: CustomError}); + expectType(error3); + const error4 = await t.throwsAsync(async () => {}, {is: new CustomError()}); + expectType(error4); + const error5 = await t.throwsAsync(async () => {}, {instanceOf: CustomError, is: new CustomError()}); + expectType(error5); }); diff --git a/test-types/module/throws.ts b/test-types/module/throws.ts index 126dd174b..088cce716 100644 --- a/test-types/module/throws.ts +++ b/test-types/module/throws.ts @@ -12,15 +12,30 @@ class CustomError extends Error { } test('throws', t => { - expectType(t.throws(() => {})); + const error1 = t.throws(() => {}); + expectType(error1); const error2: CustomError | undefined = t.throws(() => {}); expectType(error2); expectType(t.throws(() => {})); + const error3 = t.throws(() => {}, {instanceOf: CustomError}); + expectType(error3); + const error4 = t.throws(() => {}, {is: new CustomError()}); + expectType(error4); + const error5 = t.throws(() => {}, {instanceOf: CustomError, is: new CustomError()}); + expectType(error5); }); test('throwsAsync', async t => { - expectType(await t.throwsAsync(async () => {})); + const error1 = await t.throwsAsync(async () => {}); + expectType(error1); expectType(await t.throwsAsync(async () => {})); - expectType(await t.throwsAsync(Promise.reject())); + const error2 = await t.throwsAsync(Promise.reject()); + expectType(error2); expectType(await t.throwsAsync(Promise.reject())); + const error3 = await t.throwsAsync(async () => {}, {instanceOf: CustomError}); + expectType(error3); + const error4 = await t.throwsAsync(async () => {}, {is: new CustomError()}); + expectType(error4); + const error5 = await t.throwsAsync(async () => {}, {instanceOf: CustomError, is: new CustomError()}); + expectType(error5); }); diff --git a/types/assertions.d.cts b/types/assertions.d.cts index 08c3e05f1..3fbd37556 100644 --- a/types/assertions.d.cts +++ b/types/assertions.d.cts @@ -1,15 +1,20 @@ -export type ErrorConstructor = new (...args: any[]) => Error; +export type ErrorConstructor = { + new (...args: any[]): ErrorType; + readonly prototype: ErrorType; +} + +export type ThrownError = ErrorType extends ErrorConstructor ? ErrorType['prototype'] : ErrorType; /** Specify one or more expectations the thrown error must satisfy. */ -export type ThrowsExpectation = { +export type ThrowsExpectation = { /** The thrown error must have a code that equals the given string or number. */ code?: string | number; /** The thrown error must be an instance of this constructor. */ - instanceOf?: ErrorConstructor; + instanceOf?: ErrorType extends ErrorConstructor ? ErrorType : ErrorType extends Error ? ErrorConstructor : never; /** The thrown error must be strictly equal to this value. */ - is?: Error; + is?: ErrorType extends ErrorConstructor ? ErrorType['prototype'] : ErrorType; /** The thrown error must have a message that equals the given string, or matches the regular expression. */ message?: string | RegExp | ((message: string) => boolean); @@ -293,7 +298,7 @@ export type ThrowsAssertion = { * Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value. * The error must satisfy all expectations. Returns undefined when the assertion fails. */ - (fn: () => any, expectations?: ThrowsExpectation, message?: string): ThrownError | undefined; + (fn: () => any, expectations?: ThrowsExpectation, message?: string): ThrownError | undefined; /** Skip this assertion. */ skip(fn: () => any, expectations?: any, message?: string): void; @@ -304,14 +309,14 @@ export type ThrowsAsyncAssertion = { * Assert that the async function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error * value. Returns undefined when the assertion fails. You must await the result. The error must satisfy all expectations. */ - (fn: () => PromiseLike, expectations?: ThrowsExpectation, message?: string): Promise; + (fn: () => PromiseLike, expectations?: ThrowsExpectation, message?: string): Promise | undefined>; /** * Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the * rejection reason. Returns undefined when the assertion fails. You must await the result. The error must satisfy all * expectations. */ - (promise: PromiseLike, expectations?: ThrowsExpectation, message?: string): Promise; + (promise: PromiseLike, expectations?: ThrowsExpectation, message?: string): Promise | undefined>; /** Skip this assertion. */ skip(thrower: any, expectations?: any, message?: string): void;