as-test 1.0.16 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Change Log
2
2
 
3
+ ## v1.1.1
4
+
5
+ - add `ast clean` command to remove build outputs, coverage outputs, crash reports, and logs.
6
+ - remove deps
7
+
8
+ ## v1.1.0
9
+
10
+ ### Upgrading to 1.1.0
11
+
12
+ - refresh generated runners with:
13
+
14
+ ```bash
15
+ rm -rf .as-test/runners && npx as-test init
16
+ ```
17
+
18
+ - generated runners now use a single file per target and import `instantiate(...)` from `as-test/lib`
19
+ - new bindings and web runners no longer use `*.hooks.js`
20
+ - named modes now support `default: false` to make a mode manual-only
21
+ - the repo examples and default config now use mode names like `node:wasi`, `node:bindings`, `chromium`, and `chromium:headless`
22
+
23
+ ### Runtime & Runners
24
+
25
+ - feat: replace the split bindings/web hooks model with single-file runners that import `instantiate(...)` from `as-test/lib`, keeping bindings, WASI, and web runner syntax aligned.
26
+ - feat: add `as-test/lib` as the shared JS runtime host layer for bindings, WASI, and web targets, with runtime artifact resolution happening out of sight before runner execution.
27
+ - feat: autodetect bindings helper shape at runtime support level (`raw`, `esm`, or `none`) and keep the generated runner surface minimal.
28
+ - fix: make `ast run` rebuild missing artifacts on demand instead of failing when only some selected outputs already exist.
29
+ - fix: report real lazy-build time in `ast run` summaries instead of always printing `0us build`.
30
+ - fix: remove build artifact copy/reuse shortcuts so each selected file/mode compiles directly, avoiding stale output reuse across modes.
31
+
32
+ ### Web Runtime
33
+
34
+ - feat: move headful web execution to a persistent single-browser-session architecture that opens one page, runs multiple binaries through it, and keeps browser-side runtime details hidden from the runner file.
35
+ - feat: redesign the non-headless browser page into a minimal macOS-inspired loading surface with light/dark mode support and simpler status messaging.
36
+ - feat: make headful web runs wait for the user to open the local session URL, and expose a browser-side exit control.
37
+ - fix: keep all browser bootstrap, asset, and websocket traffic on one local port.
38
+ - fix: improve browser discovery and launch behavior across Chromium, Firefox, and WebKit, including Playwright cache lookup, macOS app bundle resolution, paths with spaces, and owned-process teardown.
39
+ - fix: fail terminal-side runs when the browser side disconnects unexpectedly, and close the browser side when the websocket is lost.
40
+
41
+ ### WASI
42
+
43
+ - fix: make the WASI stdin transport retry only on retryable WASI read errors (`AGAIN` and `INTR`), which resolves intermittent snapshot reply corruption in `node:wasi` runs.
44
+
45
+ ### Modes & CLI
46
+
47
+ - feat: add per-mode `default: boolean` selection so modes can be included in implicit runs or kept manual-only.
48
+ - feat: add `ast clean` to remove configured build outputs, coverage outputs, crash reports, and logs.
49
+ - feat: make `ast clean` remove everything by default, prompt with `[Y/n]` before a full clean, and allow `-f` / `--force` to skip that confirmation.
50
+ - fix: restore unnamed root-config execution alongside named default modes when `--mode` is omitted.
51
+ - fix: make `ast clean` ignore mode `default: false` flags and treat an omitted `--mode` as a full clean across every configured mode.
52
+ - fix: make `ast clean --mode ...` stay scoped to the selected mode(s) and skip shared output paths that are still owned by unselected modes instead of deleting them.
53
+ - fix: make full `ast clean` remove the configured output roots directly so stale legacy build, coverage, and log directories are removed too.
54
+ - fix: simplify `ast clean` console output so it only prints removed paths plus a final summary.
55
+
56
+ ### Tests
57
+
58
+ - feat: add integration coverage for bindings (`raw`, `esm`, `none`), WASI, and web runtime paths, including browser-resolution regressions and single-origin web runner behavior.
59
+
3
60
  ## 2026-05-08 - v1.0.16
4
61
 
5
62
  - feat: modes inherit pre-declared properties if not explicitly overriden
package/README.md CHANGED
@@ -2,6 +2,14 @@
2
2
  ╠═╣ ╚═╗ ══ ║ ╠═ ╚═╗ ║
3
3
  ╩ ╩ ╚═╝ ╩ ╚═╝ ╚═╝ ╩ </pre></h1>
4
4
 
5
+ > **Upgrading to 1.1.0**
6
+ >
7
+ > See [CHANGELOG.md](./CHANGELOG.md) for upgrade notes. In most projects, refreshing generated runners is enough:
8
+ >
9
+ > ```bash
10
+ > rm -rf .as-test/runners && npx as-test init
11
+ > ```
12
+
5
13
  <details>
6
14
  <summary>Table of Contents</summary>
7
15
 
@@ -77,7 +85,7 @@ Minimal `as-test.config.json`:
77
85
  },
78
86
  "runOptions": {
79
87
  "runtime": {
80
- "cmd": "node .as-test/runners/default.wasi.js <file>"
88
+ "cmd": "node .as-test/runners/default.wasi.js"
81
89
  }
82
90
  }
83
91
  }
@@ -323,7 +331,7 @@ For example, a simple WASI setup in `as-test.config.json` can look like this:
323
331
  },
324
332
  "runOptions": {
325
333
  "runtime": {
326
- "cmd": "node ./.as-test/runners/default.wasi.js <file>"
334
+ "cmd": "node ./.as-test/runners/default.wasi.js"
327
335
  }
328
336
  }
329
337
  }
@@ -342,22 +350,41 @@ If you want to keep more than one runtime around, use modes:
342
350
  "input": ["./assembly/__tests__/*.spec.ts"],
343
351
  "modes": {
344
352
  "wasi": {
353
+ "default": true,
345
354
  "buildOptions": {
346
355
  "target": "wasi"
347
356
  },
348
357
  "runOptions": {
349
358
  "runtime": {
350
- "cmd": "node ./.as-test/runners/default.wasi.js <file>"
359
+ "cmd": "node ./.as-test/runners/default.wasi.js"
351
360
  }
352
361
  }
353
362
  },
354
363
  "bindings": {
364
+ "default": true,
355
365
  "buildOptions": {
356
366
  "target": "bindings"
357
367
  },
358
368
  "runOptions": {
359
369
  "runtime": {
360
- "cmd": "node ./.as-test/runners/default.bindings.js <file>"
370
+ "cmd": "node ./.as-test/runners/default.bindings.js"
371
+ }
372
+ }
373
+ }
374
+ }
375
+ }
376
+ ```
377
+
378
+ Set `"default": false` on a mode when you want to keep it available for explicit `--mode ...` runs without including it in normal runs:
379
+
380
+ ```json
381
+ {
382
+ "modes": {
383
+ "web": {
384
+ "default": false,
385
+ "runOptions": {
386
+ "runtime": {
387
+ "browser": "chromium"
361
388
  }
362
389
  }
363
390
  }
@@ -365,6 +392,20 @@ If you want to keep more than one runtime around, use modes:
365
392
  }
366
393
  ```
367
394
 
395
+ With that setup:
396
+
397
+ ```bash
398
+ npx ast test
399
+ ```
400
+
401
+ runs the root/default config plus any modes whose `"default"` flag is not `false`, while:
402
+
403
+ ```bash
404
+ npx ast test --mode web
405
+ ```
406
+
407
+ still runs the `web` mode explicitly.
408
+
368
409
  Modes can also be full config objects. That means a mode can override fuzzing, input globs, output aliases, runtime, build flags, and the rest of the normal config surface:
369
410
 
370
411
  ```json
@@ -305,6 +305,11 @@
305
305
  "type": "object",
306
306
  "additionalProperties": false,
307
307
  "properties": {
308
+ "default": {
309
+ "type": "boolean",
310
+ "description": "Include this mode when --mode is omitted. Defaults to true.",
311
+ "default": true
312
+ },
308
313
  "$schema": {
309
314
  "type": "string"
310
315
  },
@@ -7,3 +7,22 @@ fuzz("bounded integer addition", (left: i32, right: i32): bool => {
7
7
  }).generate((seed: FuzzSeed, run: (left: i32, right: i32) => bool): void => {
8
8
  run(seed.i32({ min: -1000, max: 1000 }), seed.i32({ min: -1000, max: 1000 }));
9
9
  });
10
+
11
+ fuzz("numeric matchers stay consistent for bounded integers", (value: i32): bool => {
12
+ expect(value).toBeNumber();
13
+ expect(value).toBeInteger();
14
+ expect(value).toBeFinite();
15
+ expect(value).toBe(value);
16
+ expect(value).toBeGreaterOrEqualTo(-1000);
17
+ expect(value).toBeLessThanOrEqualTo(1000);
18
+
19
+ if (value != 0) {
20
+ expect(value).toBeTruthy();
21
+ } else {
22
+ expect(value).toBeFalsy();
23
+ }
24
+
25
+ return true;
26
+ }).generate((seed: FuzzSeed, run: (value: i32) => bool): void => {
27
+ run(seed.i32({ min: -1000, max: 1000 }));
28
+ }, 250);
@@ -19,3 +19,34 @@ fuzz(
19
19
  }),
20
20
  );
21
21
  }, 250);
22
+
23
+ fuzz(
24
+ "string matchers stay consistent on derived slices",
25
+ (input: string): bool => {
26
+ const split = input.length >> 1;
27
+ const prefix = input.substr(0, split);
28
+ const suffix = input.substr(split);
29
+
30
+ expect(input).toBeString();
31
+ expect(input).toContain(prefix);
32
+ expect(input).toMatch(suffix);
33
+
34
+ if (input.length > 0) {
35
+ expect(input).toBeTruthy();
36
+ expect(input).toContain(input.charAt(input.length - 1));
37
+ } else {
38
+ expect(input).toBeFalsy();
39
+ }
40
+
41
+ return true;
42
+ },
43
+ ).generate((seed: FuzzSeed, run: (input: string) => bool): void => {
44
+ run(
45
+ seed.string({
46
+ charset: "ascii",
47
+ min: 0,
48
+ max: 40,
49
+ exclude: [0x00, 0x0a, 0x0d],
50
+ }),
51
+ );
52
+ }, 250);
package/assembly/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Suite } from "./src/suite";
2
2
  import { Expectation } from "./src/expectation";
3
- import { stringify } from "as-console/stringify";
4
3
  import {
5
4
  __COVER,
6
5
  __POINTS,
@@ -16,6 +15,7 @@ import {
16
15
  sendReport,
17
16
  } from "./util/wipc";
18
17
  import { quote } from "./util/json";
18
+ import { bold, formatValue, green, red } from "./util/format";
19
19
  import {
20
20
  createFuzzer,
21
21
  FuzzerBase,
@@ -245,7 +245,7 @@ export function log<T>(data: T): void {
245
245
  }
246
246
 
247
247
  export function __as_test_log_default<T>(data: T): string {
248
- return stringify(data);
248
+ return formatValue(data);
249
249
  }
250
250
 
251
251
  export function __as_test_log_is_enabled(): bool {
@@ -673,11 +673,11 @@ export class Result {
673
673
  }
674
674
  display(): string {
675
675
  let out = "";
676
- out += `${rainbow.boldMk(this.name)} `;
676
+ out += `${bold(this.name)} `;
677
677
  if (this.arg1) {
678
- out += `${rainbow.boldMk(rainbow.red(this.arg1.toString() + " " + "failed"))}`;
678
+ out += `${bold(red(this.arg1.toString() + " failed"))}`;
679
679
  } else {
680
- out += `${rainbow.boldMk(rainbow.green("0 failed"))}`;
680
+ out += `${bold(green("0 failed"))}`;
681
681
  }
682
682
  out += ` ${this.arg1 + this.arg2} total\n`;
683
683
  return out;
@@ -62,6 +62,7 @@ export class Expectation<T> extends Tests {
62
62
  instr: string,
63
63
  left: string,
64
64
  right: string,
65
+ message: string = "",
65
66
  ): void {
66
67
  if (this._skip) {
67
68
  this.verdict = "skip";
@@ -77,7 +78,8 @@ export class Expectation<T> extends Tests {
77
78
  this.instr = instr;
78
79
  this.left = left;
79
80
  this.right = right;
80
- this.message = isFail ? this._message : "";
81
+ const resolvedMessage = message.length ? message : this._message;
82
+ this.message = isFail ? resolvedMessage : "";
81
83
  if (isFail) {
82
84
  sendAssertionFailure(this._snapshotKey, instr, left, right, this.message);
83
85
  // @ts-ignore
@@ -103,7 +105,7 @@ export class Expectation<T> extends Tests {
103
105
  /**
104
106
  * Tests if a == null
105
107
  */
106
- toBeNull(): void {
108
+ toBeNull(message: string = ""): void {
107
109
  const passed =
108
110
  (isNullable<T>() && changetype<usize>(this._left) == 0) ||
109
111
  (isInteger<T>() && nameof<T>() == "usize" && this._left == 0);
@@ -117,13 +119,14 @@ export class Expectation<T> extends Tests {
117
119
  visualize<T>(
118
120
  load<T>(changetype<usize>(this), offsetof<Expectation<T>>("_right")),
119
121
  ),
122
+ message,
120
123
  );
121
124
  }
122
125
 
123
126
  /**
124
127
  * Tests if a > b
125
128
  */
126
- toBeGreaterThan(value: T): void {
129
+ toBeGreaterThan(value: T, message: string = ""): void {
127
130
  if (!isInteger<T>() && !isFloat<T>())
128
131
  ERROR("toBeGreaterThan() can only be used on number types!");
129
132
 
@@ -139,13 +142,14 @@ export class Expectation<T> extends Tests {
139
142
  visualize<T>(
140
143
  load<T>(changetype<usize>(this), offsetof<Expectation<T>>("_right")),
141
144
  ),
145
+ message,
142
146
  );
143
147
  }
144
148
 
145
149
  /**
146
150
  * Tests if a >= b
147
151
  */
148
- toBeGreaterOrEqualTo(value: T): void {
152
+ toBeGreaterOrEqualTo(value: T, message: string = ""): void {
149
153
  if (!isInteger<T>() && !isFloat<T>())
150
154
  ERROR("toBeGreaterOrEqualTo() can only be used on number types!");
151
155
 
@@ -161,13 +165,14 @@ export class Expectation<T> extends Tests {
161
165
  visualize<T>(
162
166
  load<T>(changetype<usize>(this), offsetof<Expectation<T>>("_right")),
163
167
  ),
168
+ message,
164
169
  );
165
170
  }
166
171
 
167
172
  /**
168
173
  * Tests if a < b
169
174
  */
170
- toBeLessThan(value: T): void {
175
+ toBeLessThan(value: T, message: string = ""): void {
171
176
  if (!isInteger<T>() && !isFloat<T>())
172
177
  ERROR("toBeLessThan() can only be used on number types!");
173
178
 
@@ -183,13 +188,14 @@ export class Expectation<T> extends Tests {
183
188
  visualize<T>(
184
189
  load<T>(changetype<usize>(this), offsetof<Expectation<T>>("_right")),
185
190
  ),
191
+ message,
186
192
  );
187
193
  }
188
194
 
189
195
  /**
190
196
  * Tests if a <= b
191
197
  */
192
- toBeLessThanOrEqualTo(value: T): void {
198
+ toBeLessThanOrEqualTo(value: T, message: string = ""): void {
193
199
  if (!isInteger<T>() && !isFloat<T>())
194
200
  ERROR("toBeLessThanOrEqualTo() can only be used on number types!");
195
201
 
@@ -205,93 +211,127 @@ export class Expectation<T> extends Tests {
205
211
  visualize<T>(
206
212
  load<T>(changetype<usize>(this), offsetof<Expectation<T>>("_right")),
207
213
  ),
214
+ message,
208
215
  );
209
216
  }
210
217
 
211
218
  /**
212
219
  * Tests if a is string
213
220
  */
214
- toBeString(): void {
215
- this._resolve(isString<T>(), "toBeString", q(nameof<T>()), q("string"));
221
+ toBeString(message: string = ""): void {
222
+ this._resolve(
223
+ isString<T>(),
224
+ "toBeString",
225
+ q(nameof<T>()),
226
+ q("string"),
227
+ message,
228
+ );
216
229
  }
217
230
 
218
231
  /**
219
232
  * Tests if a is boolean
220
233
  */
221
- toBeBoolean(): void {
222
- this._resolve(isBoolean<T>(), "toBeBoolean", q(nameof<T>()), q("boolean"));
234
+ toBeBoolean(message: string = ""): void {
235
+ this._resolve(
236
+ isBoolean<T>(),
237
+ "toBeBoolean",
238
+ q(nameof<T>()),
239
+ q("boolean"),
240
+ message,
241
+ );
223
242
  }
224
243
 
225
244
  /**
226
245
  * Tests if a is array
227
246
  */
228
- toBeArray(): void {
229
- this._resolve(isArray<T>(), "toBeArray", q(nameof<T>()), q("Array<any>"));
247
+ toBeArray(message: string = ""): void {
248
+ this._resolve(
249
+ isArray<T>(),
250
+ "toBeArray",
251
+ q(nameof<T>()),
252
+ q("Array<any>"),
253
+ message,
254
+ );
230
255
  }
231
256
 
232
257
  /**
233
258
  * Tests if a is number
234
259
  */
235
- toBeNumber(): void {
260
+ toBeNumber(message: string = ""): void {
236
261
  this._resolve(
237
262
  isFloat<T>() || isInteger<T>(),
238
263
  "toBeNumber",
239
264
  q(nameof<T>()),
240
265
  q("number"),
266
+ message,
241
267
  );
242
268
  }
243
269
 
244
270
  /**
245
271
  * Tests if a is integer
246
272
  */
247
- toBeInteger(): void {
248
- this._resolve(isInteger<T>(), "toBeInteger", q(nameof<T>()), q("integer"));
273
+ toBeInteger(message: string = ""): void {
274
+ this._resolve(
275
+ isInteger<T>(),
276
+ "toBeInteger",
277
+ q(nameof<T>()),
278
+ q("integer"),
279
+ message,
280
+ );
249
281
  }
250
282
 
251
283
  /**
252
284
  * Tests if a is float
253
285
  */
254
- toBeFloat(): void {
255
- this._resolve(isFloat<T>(), "toBeFloat", q(nameof<T>()), q("float"));
286
+ toBeFloat(message: string = ""): void {
287
+ this._resolve(
288
+ isFloat<T>(),
289
+ "toBeFloat",
290
+ q(nameof<T>()),
291
+ q("float"),
292
+ message,
293
+ );
256
294
  }
257
295
 
258
296
  /**
259
297
  * Tests if a is finite
260
298
  */
261
- toBeFinite(): void {
299
+ toBeFinite(message: string = ""): void {
262
300
  // @ts-ignore
263
301
  const passed = (isFloat<T>() || isInteger<T>()) && isFinite(this._left);
264
- this._resolve(passed, "toBeFinite", q("Infinity"), q("Finite"));
302
+ this._resolve(passed, "toBeFinite", q("Infinity"), q("Finite"), message);
265
303
  }
266
304
 
267
305
  /**
268
306
  * Tests if a value is truthy
269
307
  */
270
- toBeTruthy(): void {
308
+ toBeTruthy(message: string = ""): void {
271
309
  this._resolve(
272
310
  isTruthy<T>(this._left),
273
311
  "toBeTruthy",
274
312
  q("falsy"),
275
313
  q("truthy"),
314
+ message,
276
315
  );
277
316
  }
278
317
 
279
318
  /**
280
319
  * Tests if a value is falsy
281
320
  */
282
- toBeFalsy(): void {
321
+ toBeFalsy(message: string = ""): void {
283
322
  this._resolve(
284
323
  !isTruthy<T>(this._left),
285
324
  "toBeFalsy",
286
325
  q("truthy"),
287
326
  q("falsy"),
327
+ message,
288
328
  );
289
329
  }
290
330
 
291
331
  /**
292
332
  * Tests if a floating-point number is close to expected
293
333
  */
294
- toBeCloseTo(expected: T, precision: i32 = 2): void {
334
+ toBeCloseTo(expected: T, precision: i32 = 2, message: string = ""): void {
295
335
  if (!isFloat<T>() && !isInteger<T>())
296
336
  ERROR("toBeCloseTo() can only be used on number types!");
297
337
  const factor = Math.pow(10, precision as f64);
@@ -302,67 +342,74 @@ export class Expectation<T> extends Tests {
302
342
  "toBeCloseTo",
303
343
  visualize<T>(this._left),
304
344
  visualize<T>(expected),
345
+ message,
305
346
  );
306
347
  }
307
348
 
308
349
  /**
309
350
  * Tests if a string contains substring
310
351
  */
311
- toMatch(value: string): void {
352
+ toMatch(value: string, message: string = ""): void {
312
353
  if (!isString<T>()) ERROR("toMatch() can only be used on string types!");
313
354
  // @ts-ignore
314
355
  const passed = this._left.indexOf(value) >= 0;
315
356
  // @ts-ignore
316
- this._resolve(passed, "toMatch", q(this._left as string), q(value));
357
+ this._resolve(passed, "toMatch", q(this._left as string), q(value), message);
317
358
  }
318
359
 
319
360
  /**
320
361
  * Tests if a string starts with the provided prefix.
321
362
  */
322
- toStartWith(value: string): void {
363
+ toStartWith(value: string, message: string = ""): void {
323
364
  if (!isString<T>())
324
365
  ERROR("toStartWith() can only be used on string types!");
325
366
  // @ts-ignore
326
367
  const left = this._left as string;
327
368
  const passed = left.indexOf(value) == 0;
328
- this._resolve(passed, "toStartWith", q(left), q(value));
369
+ this._resolve(passed, "toStartWith", q(left), q(value), message);
329
370
  }
330
371
 
331
372
  /**
332
373
  * Tests if a string ends with the provided suffix.
333
374
  */
334
- toEndWith(value: string): void {
375
+ toEndWith(value: string, message: string = ""): void {
335
376
  if (!isString<T>()) ERROR("toEndWith() can only be used on string types!");
336
377
  // @ts-ignore
337
378
  const left = this._left as string;
338
379
  const idx = left.lastIndexOf(value);
339
380
  const passed = idx >= 0 && idx + value.length == left.length;
340
- this._resolve(passed, "toEndWith", q(left), q(value));
381
+ this._resolve(passed, "toEndWith", q(left), q(value), message);
341
382
  }
342
383
 
343
384
  /**
344
385
  * Tests if an array has length x
345
386
  */
346
- toHaveLength(value: i32): void {
387
+ toHaveLength(value: i32, message: string = ""): void {
347
388
  // @ts-ignore
348
389
  const leftLen = this._left.length as i32;
349
390
  // @ts-ignore
350
391
  const passed = isArray<T>() && leftLen == value;
351
- this._resolve(passed, "toHaveLength", leftLen.toString(), value.toString());
392
+ this._resolve(
393
+ passed,
394
+ "toHaveLength",
395
+ leftLen.toString(),
396
+ value.toString(),
397
+ message,
398
+ );
352
399
  }
353
400
 
354
401
  /**
355
402
  * Tests if an array or string contains a value
356
403
  */
357
404
  // @ts-ignore
358
- toContain(value: valueof<T>): void {
405
+ toContain(value: valueof<T>, message: string = ""): void {
359
406
  if (isString<T>()) {
360
407
  // @ts-ignore
361
408
  const left = this._left as string;
362
409
  // @ts-ignore
363
410
  const needle = value as string;
364
411
  const passed = left.indexOf(needle) >= 0;
365
- this._resolve(passed, "toContain", q(left), q(needle));
412
+ this._resolve(passed, "toContain", q(left), q(needle), message);
366
413
  return;
367
414
  }
368
415
 
@@ -374,6 +421,7 @@ export class Expectation<T> extends Tests {
374
421
  "toContain",
375
422
  stringifyValue<T>(this._left),
376
423
  stringifyValue<valueof<T>>(value),
424
+ message,
377
425
  );
378
426
  return;
379
427
  }
@@ -385,28 +433,28 @@ export class Expectation<T> extends Tests {
385
433
  * Alias for toContain().
386
434
  */
387
435
  // @ts-ignore
388
- toContains(value: valueof<T>): void {
389
- this.toContain(value);
436
+ toContains(value: valueof<T>, message: string = ""): void {
437
+ this.toContain(value, message);
390
438
  }
391
439
 
392
440
  /**
393
441
  * Tests if serialized value matches stored snapshot.
394
442
  */
395
- toMatchSnapshot(name: string = ""): void {
443
+ toMatchSnapshot(name: string = "", message: string = ""): void {
396
444
  let key = name.length
397
445
  ? namedSnapshotKey(this._snapshotKey, name)
398
446
  : nextUnnamedSnapshotKey(this._snapshotKey);
399
447
 
400
448
  const actual = stringifyValue<T>(this._left);
401
449
  const res = snapshotAssert(key, actual);
402
- this._resolve(res.ok, "toMatchSnapshot", actual, res.expected);
450
+ this._resolve(res.ok, "toMatchSnapshot", actual, res.expected, message);
403
451
  }
404
452
 
405
453
  /**
406
454
  * Delegates throw assertions to try-as when available.
407
455
  * If try-as is unavailable, this matcher is disabled and warns once.
408
456
  */
409
- toThrow(): void {
457
+ toThrow(message: string = ""): void {
410
458
  // @ts-ignore
411
459
  if (!isDefined(AS_TEST_TRY_AS)) {
412
460
  if (!warnedToThrowDisabled) {
@@ -415,7 +463,7 @@ export class Expectation<T> extends Tests {
415
463
  );
416
464
  warnedToThrowDisabled = true;
417
465
  }
418
- this._resolve(true, "toThrow", q("disabled"), q("disabled"));
466
+ this._resolve(true, "toThrow", q("disabled"), q("disabled"), message);
419
467
  return;
420
468
  }
421
469
 
@@ -425,13 +473,13 @@ export class Expectation<T> extends Tests {
425
473
  // @ts-ignore
426
474
  __ExceptionState.Failures--;
427
475
  }
428
- this._resolve(passed, "toThrow", q("throws"), q("throws"));
476
+ this._resolve(passed, "toThrow", q("throws"), q("throws"), message);
429
477
  }
430
478
 
431
479
  /**
432
480
  * Tests for equality
433
481
  */
434
- toBe(equals: T): void {
482
+ toBe(equals: T, message: string = ""): void {
435
483
  const passed = this._left === equals;
436
484
 
437
485
  this._resolve(
@@ -439,32 +487,35 @@ export class Expectation<T> extends Tests {
439
487
  "toBe",
440
488
  stringifyValue<T>(this._left),
441
489
  stringifyValue<T>(equals),
490
+ message,
442
491
  );
443
492
  }
444
493
 
445
494
  /**
446
495
  * Tests for deep equality
447
496
  */
448
- toEqual(equals: T): void {
497
+ toEqual(equals: T, message: string = ""): void {
449
498
  const passed = valueEquals<T>(this._left, equals, false);
450
499
  this._resolve(
451
500
  passed,
452
501
  "toEqual",
453
502
  stringifyValue<T>(this._left),
454
503
  stringifyValue<T>(equals),
504
+ message,
455
505
  );
456
506
  }
457
507
 
458
508
  /**
459
509
  * Tests for strict deep equality
460
510
  */
461
- toStrictEqual(equals: T): void {
511
+ toStrictEqual(equals: T, message: string = ""): void {
462
512
  const passed = valueEquals<T>(this._left, equals, true);
463
513
  this._resolve(
464
514
  passed,
465
515
  "toStrictEqual",
466
516
  stringifyValue<T>(this._left),
467
517
  stringifyValue<T>(equals),
518
+ message,
468
519
  );
469
520
  }
470
521
  }