qunitx 0.12.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qunitx",
3
3
  "type": "module",
4
- "version": "0.12.4",
4
+ "version": "1.0.0",
5
5
  "description": "A universal test framework for testing any js file on node.js, browser or deno with QUnit API",
6
6
  "author": "Izel Nakri",
7
7
  "license": "MIT",
@@ -1,3 +1,29 @@
1
+ /**
2
+ * QUnitX — universal test library that runs the same test file in Node.js, Deno, and browser.
3
+ *
4
+ * Wraps QUnit's assertion API over each runtime's native BDD test runner so you only
5
+ * write your tests once.
6
+ *
7
+ * @example
8
+ * ```js
9
+ * import { module, test } from "qunitx";
10
+ *
11
+ * module("Math", (hooks) => {
12
+ * hooks.before((assert) => assert.step("setup"));
13
+ *
14
+ * test("addition", (assert) => {
15
+ * assert.equal(1 + 1, 2);
16
+ * });
17
+ *
18
+ * test("async", async (assert) => {
19
+ * const n = await Promise.resolve(42);
20
+ * assert.strictEqual(n, 42);
21
+ * });
22
+ * });
23
+ * ```
24
+ *
25
+ * @module
26
+ */
1
27
  import { AssertionError as DenoAssertionError } from "jsr:@std/assert";
2
28
  import '../../vendor/qunit.js';
3
29
  import Assert from '../shared/assert.js';
@@ -6,6 +32,24 @@ import TestContext from '../shared/test-context.js';
6
32
  import Module from './module.js';
7
33
  import Test from './test.js';
8
34
 
35
+ /**
36
+ * Thrown when an assertion fails. Extends Deno's built-in `AssertionError`
37
+ * so it integrates cleanly with Deno's test runner output.
38
+ *
39
+ * You rarely construct this directly — assertion methods on {@linkcode Assert}
40
+ * throw it automatically on failure.
41
+ *
42
+ * @example
43
+ * ```js
44
+ * import { AssertionError } from "qunitx";
45
+ *
46
+ * try {
47
+ * throw new AssertionError({ message: "something went wrong" });
48
+ * } catch (e) {
49
+ * console.log(e instanceof AssertionError); // true
50
+ * }
51
+ * ```
52
+ */
9
53
  export class AssertionError extends DenoAssertionError {
10
54
  constructor(object) {
11
55
  super(object.message);
@@ -21,7 +65,103 @@ Object.freeze(Assert);
21
65
  Object.freeze(ModuleContext);
22
66
  Object.freeze(TestContext);
23
67
 
68
+ export { Assert };
69
+
70
+ /**
71
+ * Defines a test module (suite). Wraps Deno's `describe()` and sets up the
72
+ * QUnit lifecycle — `before`, `beforeEach`, `afterEach`, and `after` hooks,
73
+ * assertion counting, and step tracking.
74
+ *
75
+ * Each {@linkcode test} inside the callback receives an {@linkcode Assert} instance.
76
+ * Modules can be nested by calling `module()` inside another module's callback.
77
+ *
78
+ * @param {string} moduleName - Name of the test suite.
79
+ * @param {object} [runtimeOptions] - Optional Deno BDD options forwarded to `describe()`
80
+ * (e.g. `{ concurrency: false }`, `{ permissions: { read: true } }`).
81
+ * @param {function} moduleContent - Callback that defines tests and hooks.
82
+ * Receives `(hooks, { moduleName, options })` where `hooks` exposes
83
+ * `before`, `beforeEach`, `afterEach`, and `after`.
84
+ * @example
85
+ * ```js
86
+ * import { module, test } from "qunitx";
87
+ *
88
+ * module("Math", (hooks) => {
89
+ * hooks.before((assert) => {
90
+ * assert.step("before hook ran");
91
+ * });
92
+ *
93
+ * test("addition", (assert) => {
94
+ * assert.equal(2 + 2, 4);
95
+ * });
96
+ * });
97
+ * ```
98
+ * @example
99
+ * ```js
100
+ * // Nested modules
101
+ * module("Outer", () => {
102
+ * module("Inner", () => {
103
+ * test("nested test", (assert) => {
104
+ * assert.ok(true);
105
+ * });
106
+ * });
107
+ * });
108
+ * ```
109
+ */
24
110
  export const module = Module;
111
+
112
+ /**
113
+ * Defines an individual test. Wraps Deno's `it()` and handles the full QUnit
114
+ * lifecycle: `beforeEach`/`afterEach` hooks, async assertion waiting, and step
115
+ * verification. Must be called inside a {@linkcode module} callback.
116
+ *
117
+ * The test callback receives `(assert, { testName, options })` where `assert`
118
+ * is an {@linkcode Assert} instance.
119
+ *
120
+ * @param {string} testName - Name of the test.
121
+ * @param {object} [runtimeOptions] - Optional Deno BDD options forwarded to `it()`
122
+ * (e.g. `{ concurrency: false }`, `{ sanitizeExit: false }`).
123
+ * @param {function} testContent - Test callback receiving `(assert, { testName, options })`.
124
+ * @example
125
+ * ```js
126
+ * import { module, test } from "qunitx";
127
+ *
128
+ * module("Math", () => {
129
+ * test("addition", (assert) => {
130
+ * assert.equal(1 + 1, 2);
131
+ * });
132
+ *
133
+ * test("async resolves correctly", async (assert) => {
134
+ * const result = await Promise.resolve(42);
135
+ * assert.strictEqual(result, 42);
136
+ * });
137
+ * });
138
+ * ```
139
+ */
25
140
  export const test = Test;
26
141
 
142
+ /**
143
+ * The default export provides the full QUnitX API as a single object.
144
+ *
145
+ * @example
146
+ * ```js
147
+ * import qunitx from "qunitx";
148
+ *
149
+ * qunitx.module("Math", () => {
150
+ * qunitx.test("addition", (assert) => {
151
+ * assert.equal(1 + 1, 2);
152
+ * });
153
+ * });
154
+ * ```
155
+ *
156
+ * @property {Function} module - Defines a test suite. Wraps Deno's `describe()` with
157
+ * QUnit lifecycle hooks (`before`, `beforeEach`, `afterEach`, `after`).
158
+ * See the named {@linkcode module} export for full parameter documentation.
159
+ * @property {Function} test - Defines an individual test inside a `module()` callback.
160
+ * Receives an {@linkcode Assert} instance as its first argument.
161
+ * See the named {@linkcode test} export for full parameter documentation.
162
+ * @property {typeof AssertionError} AssertionError - The error class thrown when an
163
+ * assertion fails. Extends Deno's built-in `AssertionError`.
164
+ * @property {object} config - Runtime configuration object (currently unused; reserved
165
+ * for future QUnit config compatibility).
166
+ */
27
167
  export default { AssertionError: Assert.AssertionError, module, test, config: {} };
@@ -6,6 +6,25 @@ import util from 'node:util';
6
6
  // NOTE: Another approach for a global report Make this._assertions.set(this.currentTest, (this._assertions.get(this.currentTest) || 0) + 1); for pushResult
7
7
  // NOTE: This should *always* be a singleton(?), passed around as an argument for hooks. Seems difficult with concurrency. Singleton needs to be a concurrent data structure.
8
8
 
9
+ /**
10
+ * The assertion object passed to every test callback and lifecycle hook.
11
+ *
12
+ * Every {@linkcode test} callback receives an instance of `Assert` as its first argument.
13
+ * All assertion methods throw an {@linkcode AssertionError} on failure, which the test
14
+ * runner catches and reports.
15
+ *
16
+ * @example
17
+ * ```js
18
+ * import { module, test } from "qunitx";
19
+ *
20
+ * module("Math", () => {
21
+ * test("addition", (assert) => {
22
+ * assert.equal(1 + 1, 2);
23
+ * assert.strictEqual(typeof 42, "number");
24
+ * });
25
+ * });
26
+ * ```
27
+ */
9
28
  export default class Assert {
10
29
  static QUnit;
11
30
  static AssertionError;
@@ -17,6 +36,20 @@ export default class Assert {
17
36
  _incrementAssertionCount() {
18
37
  this.test.totalExecutedAssertions++;
19
38
  }
39
+
40
+ /**
41
+ * Sets the number of milliseconds after which the current test will fail if not yet complete.
42
+ *
43
+ * @param {number} number - Timeout in milliseconds (positive integer).
44
+ * @example
45
+ * ```js
46
+ * test("slow async operation", async (assert) => {
47
+ * assert.timeout(500);
48
+ * await somethingAsync();
49
+ * assert.ok(true);
50
+ * });
51
+ * ```
52
+ */
20
53
  timeout(number) {
21
54
  if (!Number.isInteger(number) || number < 0) {
22
55
  throw new Error('assert.timeout() expects a positive integer.');
@@ -24,6 +57,22 @@ export default class Assert {
24
57
 
25
58
  this.test.timeout = number;
26
59
  }
60
+
61
+ /**
62
+ * Records a named step. Use with {@linkcode Assert.prototype.verifySteps} to assert that
63
+ * a sequence of steps occurred in the right order.
64
+ *
65
+ * @param {string} message - The step label to record.
66
+ * @example
67
+ * ```js
68
+ * test("event order", (assert) => {
69
+ * assert.expect(3);
70
+ * assert.step("step one");
71
+ * assert.step("step two");
72
+ * assert.verifySteps(["step one", "step two"]);
73
+ * });
74
+ * ```
75
+ */
27
76
  step(message) {
28
77
  let assertionMessage = message;
29
78
  let result = !!message;
@@ -42,10 +91,41 @@ export default class Assert {
42
91
  message: assertionMessage
43
92
  });
44
93
  }
94
+
95
+ /**
96
+ * Asserts that the steps recorded via {@linkcode Assert.prototype.step} match the given array,
97
+ * then clears the recorded steps.
98
+ *
99
+ * @param {string[]} steps - Expected array of step labels in order.
100
+ * @param {string} [message] - Optional failure message.
101
+ * @example
102
+ * ```js
103
+ * test("lifecycle order", (assert) => {
104
+ * assert.step("init");
105
+ * assert.step("run");
106
+ * assert.verifySteps(["init", "run"]);
107
+ * });
108
+ * ```
109
+ */
45
110
  verifySteps(steps, message = 'Verify steps failed!') {
46
111
  this.deepEqual(this.test.steps, steps, message);
47
112
  this.test.steps.length = 0;
48
113
  }
114
+
115
+ /**
116
+ * Sets the number of assertions expected to run in the current test.
117
+ * The test fails if a different number of assertions actually ran.
118
+ *
119
+ * @param {number} number - Expected assertion count (non-negative integer).
120
+ * @example
121
+ * ```js
122
+ * test("exactly two assertions", (assert) => {
123
+ * assert.expect(2);
124
+ * assert.ok(true);
125
+ * assert.ok(true);
126
+ * });
127
+ * ```
128
+ */
49
129
  expect(number) {
50
130
  if (!Number.isInteger(number) || number < 0) {
51
131
  throw new Error('assert.expect() expects a positive integer.');
@@ -53,6 +133,25 @@ export default class Assert {
53
133
 
54
134
  this.test.expectedAssertionCount = number;
55
135
  }
136
+
137
+ /**
138
+ * Returns a `done` callback for callback-style async tests. The test will not
139
+ * finish until every `done` callback returned by `async()` has been called.
140
+ *
141
+ * For `async/await` tests prefer `async (assert) => { ... }` directly.
142
+ *
143
+ * @returns {function} A callback to invoke when the async work finishes.
144
+ * @example
145
+ * ```js
146
+ * test("async callback style", (assert) => {
147
+ * const done = assert.async();
148
+ * setTimeout(() => {
149
+ * assert.ok(true, "async callback ran");
150
+ * done();
151
+ * }, 10);
152
+ * });
153
+ * ```
154
+ */
56
155
  async() {
57
156
  let resolveFn;
58
157
  const done = new Promise(resolve => { resolveFn = resolve; });
@@ -61,9 +160,30 @@ export default class Assert {
61
160
 
62
161
  return () => { resolveFn(); };
63
162
  }
163
+
64
164
  waitForAsyncOps() {
65
165
  return Promise.all(this.test.asyncOps);
66
166
  }
167
+
168
+ /**
169
+ * Pushes a custom assertion result. Fails the test if `resultInfo.result` is falsy.
170
+ * Throws an {@linkcode AssertionError} on failure.
171
+ *
172
+ * Useful for building custom assertion helpers.
173
+ *
174
+ * @param {{ result: boolean, actual?: unknown, expected?: unknown, message?: string }} resultInfo
175
+ * @example
176
+ * ```js
177
+ * test("custom assertion", (assert) => {
178
+ * assert.pushResult({
179
+ * result: 1 + 1 === 2,
180
+ * actual: 2,
181
+ * expected: 2,
182
+ * message: "custom math check",
183
+ * });
184
+ * });
185
+ * ```
186
+ */
67
187
  pushResult(resultInfo = {}) {
68
188
  this._incrementAssertionCount();
69
189
  if (!resultInfo.result) {
@@ -77,6 +197,19 @@ export default class Assert {
77
197
 
78
198
  return this;
79
199
  }
200
+
201
+ /**
202
+ * Asserts that `state` is truthy.
203
+ *
204
+ * @param {unknown} state - The value to test.
205
+ * @param {string} [message] - Optional failure message.
206
+ * @example
207
+ * ```js
208
+ * assert.ok(true);
209
+ * assert.ok(1, "non-zero is truthy");
210
+ * assert.ok("hello");
211
+ * ```
212
+ */
80
213
  ok(state, message) {
81
214
  this._incrementAssertionCount();
82
215
  if (!state) {
@@ -88,6 +221,19 @@ export default class Assert {
88
221
  });
89
222
  }
90
223
  }
224
+
225
+ /**
226
+ * Asserts that `state` is falsy.
227
+ *
228
+ * @param {unknown} state - The value to test.
229
+ * @param {string} [message] - Optional failure message.
230
+ * @example
231
+ * ```js
232
+ * assert.notOk(false);
233
+ * assert.notOk(0, "zero is falsy");
234
+ * assert.notOk(null);
235
+ * ```
236
+ */
91
237
  notOk(state, message) {
92
238
  this._incrementAssertionCount();
93
239
  if (state) {
@@ -99,6 +245,18 @@ export default class Assert {
99
245
  });
100
246
  }
101
247
  }
248
+
249
+ /**
250
+ * Asserts that `state === true` (strict boolean true).
251
+ *
252
+ * @param {unknown} state - The value to test.
253
+ * @param {string} [message] - Optional failure message.
254
+ * @example
255
+ * ```js
256
+ * assert.true(1 === 1);
257
+ * assert.true(Array.isArray([]), "arrays are arrays");
258
+ * ```
259
+ */
102
260
  true(state, message) {
103
261
  this._incrementAssertionCount();
104
262
  if (state !== true) {
@@ -110,6 +268,18 @@ export default class Assert {
110
268
  });
111
269
  }
112
270
  }
271
+
272
+ /**
273
+ * Asserts that `state === false` (strict boolean false).
274
+ *
275
+ * @param {unknown} state - The value to test.
276
+ * @param {string} [message] - Optional failure message.
277
+ * @example
278
+ * ```js
279
+ * assert.false(1 === 2);
280
+ * assert.false(Number.isNaN(42), "42 is not NaN");
281
+ * ```
282
+ */
113
283
  false(state, message) {
114
284
  this._incrementAssertionCount();
115
285
  if (state !== false) {
@@ -121,6 +291,22 @@ export default class Assert {
121
291
  });
122
292
  }
123
293
  }
294
+
295
+ /**
296
+ * Asserts that `actual == expected` (loose equality, allows type coercion).
297
+ *
298
+ * Prefer {@linkcode Assert.prototype.strictEqual} for most comparisons. Use {@linkcode Assert.prototype.notEqual}
299
+ * for the inverse.
300
+ *
301
+ * @param {unknown} actual - The value produced by the code under test.
302
+ * @param {unknown} expected - The expected value.
303
+ * @param {string} [message] - Optional failure message.
304
+ * @example
305
+ * ```js
306
+ * assert.equal(1, 1);
307
+ * assert.equal("1", 1, "loose equality allows coercion");
308
+ * ```
309
+ */
124
310
  equal(actual, expected, message) {
125
311
  this._incrementAssertionCount();
126
312
  if (actual != expected) {
@@ -133,6 +319,19 @@ export default class Assert {
133
319
  });
134
320
  }
135
321
  }
322
+
323
+ /**
324
+ * Asserts that `actual != expected` (loose inequality). Inverse of {@linkcode Assert.prototype.equal}.
325
+ *
326
+ * @param {unknown} actual - The actual value.
327
+ * @param {unknown} expected - The value it should not loosely equal.
328
+ * @param {string} [message] - Optional failure message.
329
+ * @example
330
+ * ```js
331
+ * assert.notEqual(1, 2);
332
+ * assert.notEqual("hello", "world");
333
+ * ```
334
+ */
136
335
  notEqual(actual, expected, message) {
137
336
  this._incrementAssertionCount();
138
337
  if (actual == expected) {
@@ -145,6 +344,23 @@ export default class Assert {
145
344
  });
146
345
  }
147
346
  }
347
+
348
+ /**
349
+ * Asserts that `actual` and `expected` have the same own enumerable properties
350
+ * and values. Prototype methods are ignored; only own properties are compared.
351
+ *
352
+ * @param {object} actual - The actual object.
353
+ * @param {object} expected - The expected object.
354
+ * @param {string} [message] - Optional failure message.
355
+ * @example
356
+ * ```js
357
+ * assert.propEqual({ a: 1, b: 2 }, { a: 1, b: 2 });
358
+ *
359
+ * // Ignores prototype methods — only own properties matter:
360
+ * function Point(x, y) { this.x = x; this.y = y; }
361
+ * assert.propEqual(new Point(1, 2), { x: 1, y: 2 });
362
+ * ```
363
+ */
148
364
  propEqual(actual, expected, message) {
149
365
  this._incrementAssertionCount();
150
366
  const targetActual = objectValues(actual);
@@ -158,6 +374,20 @@ export default class Assert {
158
374
  });
159
375
  }
160
376
  }
377
+
378
+ /**
379
+ * Asserts that `actual` and `expected` do NOT have the same own enumerable
380
+ * properties and values. Inverse of {@linkcode Assert.prototype.propEqual}.
381
+ *
382
+ * @param {object} actual - The actual object.
383
+ * @param {object} expected - The value it should not propEqual.
384
+ * @param {string} [message] - Optional failure message.
385
+ * @example
386
+ * ```js
387
+ * assert.notPropEqual({ a: 1 }, { a: 2 });
388
+ * assert.notPropEqual({ a: 1, b: 2 }, { a: 1 }); // extra key makes them unequal
389
+ * ```
390
+ */
161
391
  notPropEqual(actual, expected, message) {
162
392
  this._incrementAssertionCount();
163
393
  const targetActual = objectValues(actual);
@@ -171,6 +401,20 @@ export default class Assert {
171
401
  });
172
402
  }
173
403
  }
404
+
405
+ /**
406
+ * Asserts that `actual` contains all own enumerable properties from `expected`
407
+ * with matching values. Extra properties on `actual` are allowed and ignored.
408
+ *
409
+ * @param {object} actual - The actual object (may have extra keys).
410
+ * @param {object} expected - The subset of key/value pairs that must be present.
411
+ * @param {string} [message] - Optional failure message.
412
+ * @example
413
+ * ```js
414
+ * assert.propContains({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 });
415
+ * assert.propContains(user, { role: "admin" });
416
+ * ```
417
+ */
174
418
  propContains(actual, expected, message) {
175
419
  this._incrementAssertionCount();
176
420
  const targetActual = objectValuesSubset(actual, expected);
@@ -184,6 +428,20 @@ export default class Assert {
184
428
  });
185
429
  }
186
430
  }
431
+
432
+ /**
433
+ * Asserts that `actual` does NOT contain all own enumerable properties
434
+ * from `expected` with matching values. Inverse of {@linkcode Assert.prototype.propContains}.
435
+ *
436
+ * @param {object} actual - The actual object.
437
+ * @param {object} expected - The subset of properties that must NOT all match.
438
+ * @param {string} [message] - Optional failure message.
439
+ * @example
440
+ * ```js
441
+ * assert.notPropContains({ a: 1, b: 2 }, { a: 9 });
442
+ * assert.notPropContains(user, { role: "banned" });
443
+ * ```
444
+ */
187
445
  notPropContains(actual, expected, message) {
188
446
  this._incrementAssertionCount();
189
447
  const targetActual = objectValuesSubset(actual, expected);
@@ -197,6 +455,20 @@ export default class Assert {
197
455
  });
198
456
  }
199
457
  }
458
+
459
+ /**
460
+ * Asserts deep equality between `actual` and `expected` using recursive structural
461
+ * comparison. Handles nested objects, arrays, `Date`, `RegExp`, and more.
462
+ *
463
+ * @param {unknown} actual - The actual value.
464
+ * @param {unknown} expected - The expected value.
465
+ * @param {string} [message] - Optional failure message.
466
+ * @example
467
+ * ```js
468
+ * assert.deepEqual([1, { a: 2 }], [1, { a: 2 }]);
469
+ * assert.deepEqual(new Date("2024-01-01"), new Date("2024-01-01"));
470
+ * ```
471
+ */
200
472
  deepEqual(actual, expected, message) {
201
473
  this._incrementAssertionCount();
202
474
  if (!Assert.QUnit.equiv(actual, expected)) {
@@ -209,6 +481,19 @@ export default class Assert {
209
481
  });
210
482
  }
211
483
  }
484
+
485
+ /**
486
+ * Asserts that `actual` and `expected` are NOT deeply equal. Inverse of {@linkcode Assert.prototype.deepEqual}.
487
+ *
488
+ * @param {unknown} actual - The actual value.
489
+ * @param {unknown} expected - The value it should not deepEqual.
490
+ * @param {string} [message] - Optional failure message.
491
+ * @example
492
+ * ```js
493
+ * assert.notDeepEqual([1, 2], [1, 3]);
494
+ * assert.notDeepEqual({ a: 1 }, { a: 2 });
495
+ * ```
496
+ */
212
497
  notDeepEqual(actual, expected, message) {
213
498
  this._incrementAssertionCount();
214
499
  if (Assert.QUnit.equiv(actual, expected)) {
@@ -221,6 +506,19 @@ export default class Assert {
221
506
  });
222
507
  }
223
508
  }
509
+
510
+ /**
511
+ * Asserts that `actual === expected` (strict equality, no type coercion).
512
+ *
513
+ * @param {unknown} actual - The actual value.
514
+ * @param {unknown} expected - The expected value.
515
+ * @param {string} [message] - Optional failure message.
516
+ * @example
517
+ * ```js
518
+ * assert.strictEqual(1 + 1, 2);
519
+ * assert.strictEqual(typeof "hello", "string");
520
+ * ```
521
+ */
224
522
  strictEqual(actual, expected, message) {
225
523
  this._incrementAssertionCount();
226
524
  if (actual !== expected) {
@@ -233,6 +531,19 @@ export default class Assert {
233
531
  });
234
532
  }
235
533
  }
534
+
535
+ /**
536
+ * Asserts that `actual !== expected` (strict inequality). Inverse of {@linkcode Assert.prototype.strictEqual}.
537
+ *
538
+ * @param {unknown} actual - The actual value.
539
+ * @param {unknown} expected - The value it should not strictly equal.
540
+ * @param {string} [message] - Optional failure message.
541
+ * @example
542
+ * ```js
543
+ * assert.notStrictEqual(1, "1", "different types");
544
+ * assert.notStrictEqual({}, {}, "different object references");
545
+ * ```
546
+ */
236
547
  notStrictEqual(actual, expected, message) {
237
548
  this._incrementAssertionCount();
238
549
  if (actual === expected) {
@@ -245,6 +556,22 @@ export default class Assert {
245
556
  });
246
557
  }
247
558
  }
559
+
560
+ /**
561
+ * Asserts that `blockFn` throws an exception. Optionally validates the thrown
562
+ * error against a string (message substring), RegExp (message pattern),
563
+ * or constructor (`instanceof` check). For async functions use {@linkcode Assert.prototype.rejects}.
564
+ *
565
+ * @param {function} blockFn - A synchronous function expected to throw.
566
+ * @param {string|RegExp|function} [expected] - Optional matcher for the thrown error.
567
+ * @param {string} [message] - Optional failure message.
568
+ * @example
569
+ * ```js
570
+ * assert.throws(() => { throw new Error("boom"); });
571
+ * assert.throws(() => JSON.parse("{bad}"), SyntaxError);
572
+ * assert.throws(() => { throw new Error("bad input"); }, /bad input/);
573
+ * ```
574
+ */
248
575
  throws(blockFn, expectedInput, assertionMessage) {
249
576
  this?._incrementAssertionCount();
250
577
  const [expected, message] = validateExpectedExceptionArgs(expectedInput, assertionMessage, 'rejects');
@@ -280,6 +607,22 @@ export default class Assert {
280
607
  stackStartFn: this.throws,
281
608
  });
282
609
  }
610
+
611
+ /**
612
+ * Asserts that a promise rejects. Optionally validates the rejection reason
613
+ * against a string (message substring), RegExp (message pattern),
614
+ * or constructor (`instanceof` check). For synchronous throws use {@linkcode Assert.prototype.throws}.
615
+ *
616
+ * @param {Promise<unknown>} promise - A promise expected to reject.
617
+ * @param {string|RegExp|function} [expected] - Optional matcher for the rejection reason.
618
+ * @param {string} [message] - Optional failure message.
619
+ * @example
620
+ * ```js
621
+ * await assert.rejects(Promise.reject(new Error("oops")));
622
+ * await assert.rejects(fetch("/bad-url"), TypeError);
623
+ * await assert.rejects(Promise.reject(new Error("timeout")), /timeout/);
624
+ * ```
625
+ */
283
626
  async rejects(promise, expectedInput, assertionMessage) {
284
627
  this._incrementAssertionCount();
285
628
  const [expected, message] = validateExpectedExceptionArgs(expectedInput, assertionMessage, 'rejects');