Promise Rejections

The first thing to realize is that with the .rejects.toThrow() API we are creating a chain. The rejects part becomes a property that returns another object with the .toThrow() function. I reflect this in the Assertions type straight away:
interface Assertions {
	toBe(expected: unknown): void
	rejects: {
		toThrow(expected: Error): Promise<void>
	}
}
Note that the .rejects.toThrow() assertion will be returning a Promise. Annotate its return type accordingly (Promise<void>).
Next, I extend the object returned from the globalThis.expect function to have the rejects property. In that property, I define a new toThrow function.
globalThis.expect = function (actual: unknown) {
	return {
		toBe(expected: unknown) {
			if (actual !== expected) {
				throw new Error(`Expected ${actual} to equal to ${expected}`)
			}
		},
		rejects: {
			toThrow(expected) {
Although the actual value will be a Promise in our case, we can pass anything to the expect() function. If we assume it's a Promise, and write actual.catch(), TypeScript will kindly warn us that actual is unknown, and it doesn't necessarily have the .then()/.catch() methods a Promise has.
I make sure that the passed actual value is the instance of Promise:
rejects: {
	toThrow(expected) {
		if (!(actual instanceof Promise)) {
			throw new Error(`Expected ${actual} to be a promise`)
		}
Now that we are always asserting on a Promise, I will make sure it rejects, and compare the expected and the actual error messages once it does.
rejects: {
  toThrow(expected) {
    if (!(actual instanceof Promise)) {
			throw new Error(`Expected ${actual} to be a promise`)
		}

    return actual.catch((error) => {
      if (error.message !== expected.message) {
        throw new Error(`Expected error message to be ${error.message} but got ${expected}`)
      }
    })
  }
}
In the video, I made a mistake at 02:00! The handling of fasle-positive scenarios must be done via .then(onFulfilled, onRejected) callback, not via the .then().catch() chaining. See the correct implementation below, and feel free to skip to 02:28 to watch the rest of the solution.
To handle the unwanted cases when the actual promise resolves, I will replace the .catch() callback with a single .then() callback, providing it with two arguments:
return actual.catch(onRejected)
return actual.then(onFulfilled, onRejected)
The onFulfilled function will be executed when the actual promise resolves, and I will throw an error if that happens. The onRejected function will be the same we've provided to the .catch() method before.
rejects: {
	toThrow(expected) {
		if (!(actual instanceof Promise)) {
			throw new Error(`Expected ${actual} to be a promise`)
		}

		return actual.then(
			() => {
				throw new Error(`Expected ${actual} to reject but it didn't`)
			},
			error => {
				if (error.message !== expected.message) {
					throw new Error(
						`Expected ${error.message} to equal to ${expected.message}`,
					)
				}
			},
		)
	},
Using a single .then() callback allow me to handle the promise fulfillment/rejection without introducing a chain. You can learn more about why this is important in this issue.
Finally, I change the test case to use the newly created .rejects.toThrow() assertion and provide the expected error:
test('throws on greeting user with undefined user response', async () => {
	await expect(greetByResponse(undefined)).rejects.toThrow(
		new Error('Failed to greet the user: no user response provided'),
	)
})
Notice that we have to await the expect() call because the .toThrow() function returns a promise.
And I verify that the newly introduced behavior of the greetByResponse() function behaves as intended by running the tests:
npx tsx --import ./setup.ts greet.test.ts
✓ returns a greeting message for the given name
✓ returns a congratulation message for the given name
✓ throws on greeting user with undefined user response
✓ returns a greeting message for the given user response

Preview for dev type of none not supported.