From feaee1bd70801da524c0f67d30cc65f3614075e8 Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Thu, 11 Dec 2025 01:08:31 +0100 Subject: [PATCH 1/2] fix: ErrorConstructor typing --- src/assert.ts | 26 +++++++------- src/types.ts | 6 ++++ tests/assert/assert.spec.ts | 67 +++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/assert.ts b/src/assert.ts index c06d2ab..220d685 100644 --- a/src/assert.ts +++ b/src/assert.ts @@ -11,7 +11,7 @@ import { assert, Assertion } from 'chai' import Macroable from '@poppinss/macroable' import { AssertionError } from 'assertion-error' -import type { AssertContract, ChaiAssert } from './types.js' +import type { AnyErrorConstructor, AssertContract, ChaiAssert } from './types.js' /** * The Assert class is derived from chai.assert to allow support @@ -1170,16 +1170,16 @@ export class Assert extends Macroable implements AssertContract { * assert.throws(foo, 'failed') // fails */ throws(fn: () => void, message?: string): void - throws(fn: () => void, errType: RegExp | ErrorConstructor, message?: string): void + throws(fn: () => void, errType: RegExp | AnyErrorConstructor, message?: string): void throws( fn: () => void, - constructor: ErrorConstructor, + constructor: AnyErrorConstructor, regExp: RegExp | string, message?: string ): void throws( fn: () => void, - errType?: RegExp | ErrorConstructor | string, + errType?: RegExp | AnyErrorConstructor | string, regExp?: RegExp | string, message?: string ): void { @@ -1203,16 +1203,16 @@ export class Assert extends Macroable implements AssertContract { */ doesNotThrow(fn: () => void, message?: string): void doesNotThrow(fn: () => void, regExp: RegExp): void - doesNotThrow(fn: () => void, constructor: ErrorConstructor, message?: string): void + doesNotThrow(fn: () => void, constructor: AnyErrorConstructor, message?: string): void doesNotThrow( fn: () => void, - constructor: ErrorConstructor, + constructor: AnyErrorConstructor, regExp: RegExp | string, message?: string ): void doesNotThrow( fn: () => void, - errType?: RegExp | ErrorConstructor | string, + errType?: RegExp | AnyErrorConstructor | string, regExp?: RegExp | string, message?: string ): void { @@ -1919,18 +1919,18 @@ export class Assert extends Macroable implements AssertContract { async rejects(fn: () => void, message?: string): Promise async rejects( fn: () => void | Promise, - errType: RegExp | ErrorConstructor, + errType: RegExp | AnyErrorConstructor, message?: string ): Promise async rejects( fn: () => void | Promise, - constructor: ErrorConstructor, + constructor: AnyErrorConstructor, regExp: RegExp | string, message?: string ): Promise async rejects( fn: () => void | Promise, - errType?: RegExp | ErrorConstructor | string, + errType?: RegExp | AnyErrorConstructor | string, regExp?: RegExp | string, message?: string ): Promise { @@ -2059,18 +2059,18 @@ export class Assert extends Macroable implements AssertContract { async doesNotReject(fn: () => void, message?: string): Promise async doesNotReject( fn: () => void | Promise, - errType: RegExp | ErrorConstructor, + errType: RegExp | AnyErrorConstructor, message?: string ): Promise async doesNotReject( fn: () => void | Promise, - constructor: ErrorConstructor, + constructor: AnyErrorConstructor, regExp: RegExp | string, message?: string ): Promise async doesNotReject( fn: () => void | Promise, - errType?: RegExp | ErrorConstructor | string, + errType?: RegExp | AnyErrorConstructor | string, regExp?: RegExp | string, message?: string ): Promise { diff --git a/src/types.ts b/src/types.ts index 80d18e2..009aaaa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -75,3 +75,9 @@ export type AssertContract = Omit< > export type PluginConfig = {} + +/** + * A more flexible error constructor than `ErrorConstructor` type that allows custom + * error classes with any constructor signature + */ +export type AnyErrorConstructor = new (...args: any[]) => Error diff --git a/tests/assert/assert.spec.ts b/tests/assert/assert.spec.ts index ca6ffb3..d891046 100644 --- a/tests/assert/assert.spec.ts +++ b/tests/assert/assert.spec.ts @@ -1634,6 +1634,37 @@ test.describe('assert', function () { }, 'blah: expected {} to be a function') }) + test('throws with custom error constructor with multiple arguments', function () { + const assert = new Assert() + + class CustomError extends Error { + constructor( + public code: number, + message: string + ) { + super(message) + } + } + + assert.throws(() => { + throw new CustomError(500, 'Internal Server Error') + }, CustomError) + + assert.throws( + () => { + throw new CustomError(404, 'Not Found') + }, + CustomError, + 'Not Found' + ) + + expectError(() => { + assert.throws(() => { + throw new CustomError(500, 'Internal Server Error') + }, TypeError) + }, "expected [Function] to throw 'TypeError' but 'Error: Internal Server Error' was thrown") + }) + test('rejects', async function () { const assert = new Assert() @@ -1729,6 +1760,42 @@ test.describe('assert', function () { }, 'blah: expected {} to be a function') }) + test('rejects with custom error constructor with multiple arguments', async function () { + const assert = new Assert() + + class CustomError extends Error { + constructor( + public code: number, + message: string + ) { + super(message) + } + } + + await assert.rejects(async function () { + throw new CustomError(500, 'Internal Server Error') + }, CustomError) + + await assert.rejects( + async function () { + throw new CustomError(404, 'Not Found') + }, + CustomError, + 'Not Found' + ) + }) + + test('rejects with function returning non-void value', async function () { + const assert = new Assert() + + async function myThing(): Promise { + throw new Error('Something went wrong') + } + + await assert.rejects(() => myThing(), Error) + await assert.rejects(() => myThing(), 'Something went wrong') + }) + test('doesNotThrows', function () { const assert = new Assert() From f3f2c96ca202f98922e3c47e022ccfbe5bffa5f2 Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Thu, 11 Dec 2025 01:12:18 +0100 Subject: [PATCH 2/2] fix: throws/reject function return types --- src/assert.ts | 34 +++++++++++++++++----------------- tests/assert/assert.spec.ts | 11 +++++++++++ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/assert.ts b/src/assert.ts index 220d685..02b0864 100644 --- a/src/assert.ts +++ b/src/assert.ts @@ -1169,16 +1169,16 @@ export class Assert extends Macroable implements AssertContract { * assert.throws(foo, 'blow up') // passes * assert.throws(foo, 'failed') // fails */ - throws(fn: () => void, message?: string): void - throws(fn: () => void, errType: RegExp | AnyErrorConstructor, message?: string): void + throws(fn: () => unknown, message?: string): void + throws(fn: () => unknown, errType: RegExp | AnyErrorConstructor, message?: string): void throws( - fn: () => void, + fn: () => unknown, constructor: AnyErrorConstructor, regExp: RegExp | string, message?: string ): void throws( - fn: () => void, + fn: () => unknown, errType?: RegExp | AnyErrorConstructor | string, regExp?: RegExp | string, message?: string @@ -1201,17 +1201,17 @@ export class Assert extends Macroable implements AssertContract { * assert.doesNotThrow(foo, 'failed') // passes * assert.doesNotThrow(() => {}) // passes */ - doesNotThrow(fn: () => void, message?: string): void - doesNotThrow(fn: () => void, regExp: RegExp): void - doesNotThrow(fn: () => void, constructor: AnyErrorConstructor, message?: string): void + doesNotThrow(fn: () => unknown, message?: string): void + doesNotThrow(fn: () => unknown, regExp: RegExp): void + doesNotThrow(fn: () => unknown, constructor: AnyErrorConstructor, message?: string): void doesNotThrow( - fn: () => void, + fn: () => unknown, constructor: AnyErrorConstructor, regExp: RegExp | string, message?: string ): void doesNotThrow( - fn: () => void, + fn: () => unknown, errType?: RegExp | AnyErrorConstructor | string, regExp?: RegExp | string, message?: string @@ -1916,20 +1916,20 @@ export class Assert extends Macroable implements AssertContract { * @example * await assert.reject(() => throw new Error('')) */ - async rejects(fn: () => void, message?: string): Promise + async rejects(fn: () => unknown, message?: string): Promise async rejects( - fn: () => void | Promise, + fn: () => unknown | Promise, errType: RegExp | AnyErrorConstructor, message?: string ): Promise async rejects( - fn: () => void | Promise, + fn: () => unknown | Promise, constructor: AnyErrorConstructor, regExp: RegExp | string, message?: string ): Promise async rejects( - fn: () => void | Promise, + fn: () => unknown | Promise, errType?: RegExp | AnyErrorConstructor | string, regExp?: RegExp | string, message?: string @@ -2056,20 +2056,20 @@ export class Assert extends Macroable implements AssertContract { * async () => return 'foo', * ) // passes */ - async doesNotReject(fn: () => void, message?: string): Promise + async doesNotReject(fn: () => unknown, message?: string): Promise async doesNotReject( - fn: () => void | Promise, + fn: () => unknown | Promise, errType: RegExp | AnyErrorConstructor, message?: string ): Promise async doesNotReject( - fn: () => void | Promise, + fn: () => unknown | Promise, constructor: AnyErrorConstructor, regExp: RegExp | string, message?: string ): Promise async doesNotReject( - fn: () => void | Promise, + fn: () => unknown | Promise, errType?: RegExp | AnyErrorConstructor | string, regExp?: RegExp | string, message?: string diff --git a/tests/assert/assert.spec.ts b/tests/assert/assert.spec.ts index d891046..9db1edd 100644 --- a/tests/assert/assert.spec.ts +++ b/tests/assert/assert.spec.ts @@ -1665,6 +1665,17 @@ test.describe('assert', function () { }, "expected [Function] to throw 'TypeError' but 'Error: Internal Server Error' was thrown") }) + test('throws with function returning non-void value', function () { + const assert = new Assert() + + function myThing(): string { + throw new Error('Something went wrong') + } + + assert.throws(() => myThing(), Error) + assert.throws(() => myThing(), 'Something went wrong') + }) + test('rejects', async function () { const assert = new Assert()