vitest 5.0.0-beta.5 → 5.0.0-beta.7

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.
Files changed (76) hide show
  1. package/LICENSE.md +165 -1
  2. package/dist/browser.d.ts +27 -14
  3. package/dist/browser.js +11 -14
  4. package/dist/chunks/{artifact.C0OHWdod.js → artifact.DC5WoEgy.js} +1310 -13
  5. package/dist/chunks/{base.BPbmtl-C.js → base.B0fUxpj9.js} +17 -13
  6. package/dist/chunks/{browser.d.B-p-y0sB.d.ts → browser.d.BFOby8YL.d.ts} +3 -3
  7. package/dist/chunks/{cac.DYQRhgb-.js → cac.B41kXwyU.js} +22 -24
  8. package/dist/chunks/check.Cz_-YojU.js +15 -0
  9. package/dist/chunks/cli-api.bqNN5viV.js +519 -0
  10. package/dist/chunks/{config.d.Bkl6J5iR.d.ts → config.d.DsC1jkby.d.ts} +877 -27
  11. package/dist/chunks/{console.DjVIMaXT.js → console.omGxyKMT.js} +5 -5
  12. package/dist/chunks/{coverage.CTzCuANN.js → coverage.BVb1JqW7.js} +7 -2
  13. package/dist/chunks/coverage.BfSEMtie.js +26 -0
  14. package/dist/chunks/coverage.d.ByaAMV4A.d.ts +88 -0
  15. package/dist/chunks/{creator.CJiHagNv.js → creator.BzMFaPC-.js} +54 -30
  16. package/dist/chunks/{defaults.CUUnbOrq.js → defaults.BJUmAuaO.js} +3 -2
  17. package/dist/chunks/display.CYwyMF4S.js +192 -0
  18. package/dist/chunks/{env.BKKtU2WC.js → env.DzFJjrmK.js} +1 -1
  19. package/dist/chunks/{environment.d.CrsxCzP1.d.ts → environment.d.4-rzQgpt.d.ts} +1 -1
  20. package/dist/chunks/globals.sd6Qutmc.js +28 -0
  21. package/dist/chunks/{cli-api.SMst4llK.js → index.5krd73UD.js} +14591 -13056
  22. package/dist/chunks/{index.CXzZ7KB8.js → index.CLtI1k_4.js} +627 -214
  23. package/dist/chunks/{index.DzNNaFWy.js → index.DxR-nMjO.js} +63 -10
  24. package/dist/chunks/index.DzTmMrSM.js +940 -0
  25. package/dist/chunks/{index.DugGyGng.js → index.JaztIWgW.js} +6 -5
  26. package/dist/chunks/{init-forks.CDWjf0q6.js → init-forks.CXzT841o.js} +1 -1
  27. package/dist/chunks/{init-threads.B-vZ8N2q.js → init-threads.Ch-Bw5j3.js} +1 -1
  28. package/dist/chunks/{init.gslB1aqB.js → init.CfiYZpFg.js} +41 -25
  29. package/dist/chunks/{native.DPzPHdi5.js → native.BUCvPRKp.js} +3 -4
  30. package/dist/chunks/{nativeModuleMocker.CrcWvO03.js → nativeModuleMocker.Uot80PXb.js} +2 -3
  31. package/dist/chunks/{index.CesbTg1C.js → nativeModuleRunner.BDnwGb8g.js} +39 -3
  32. package/dist/chunks/{node.C9I1sbE9.js → node.BPMnm_Q2.js} +1 -1
  33. package/dist/chunks/offset.Dy-5Fdfn.js +24 -0
  34. package/dist/chunks/pathe.M-eThtNZ.CbK_Vbae.js +489 -0
  35. package/dist/chunks/{plugin.d.BuiCQSo8.d.ts → plugin.d.BOjbmxWW.d.ts} +1757 -1566
  36. package/dist/chunks/{plugins.DrsmdUE2.js → plugins.CsoX-42X.js} +1 -1
  37. package/dist/chunks/resolver.CERfsKE-.js +48 -0
  38. package/dist/chunks/{rpc.d.BLxNuPAq.d.ts → rpc.d.BeasqWpO.d.ts} +14 -1
  39. package/dist/chunks/{rpc.DZEh5xnQ.js → rpc.iNjF664v.js} +2 -2
  40. package/dist/chunks/setup-common.vxjAyUtK.js +36 -0
  41. package/dist/chunks/setup.DasACXvo.js +5409 -0
  42. package/dist/chunks/source-map.DZdIqpIJ.js +1776 -0
  43. package/dist/chunks/spy.B8mHA9qu.js +422 -0
  44. package/dist/chunks/{task-utils.d.CLWcZaUf.d.ts → task-utils.d.BKHiHxSN.d.ts} +20 -2
  45. package/dist/chunks/tinyrainbow.Ht9iggcq.js +95 -0
  46. package/dist/chunks/{utils.DKODp04v.js → utils.CO-iLyCr.js} +41 -42
  47. package/dist/chunks/{vm._FqKqltU.js → vm.DoQh8CN9.js} +110 -11
  48. package/dist/chunks/{worker.d.Cb0Z-SnE.d.ts → worker.d.s_RdeZtI.d.ts} +14 -11
  49. package/dist/cli.js +4 -5
  50. package/dist/config.cjs +2 -1
  51. package/dist/config.d.ts +19 -17
  52. package/dist/config.js +2 -2
  53. package/dist/index.d.ts +241 -31
  54. package/dist/index.js +11 -16
  55. package/dist/module-evaluator.d.ts +40 -8
  56. package/dist/module-evaluator.js +73 -22
  57. package/dist/node.d.ts +225 -86
  58. package/dist/node.js +30 -66
  59. package/dist/runtime.d.ts +6 -11
  60. package/dist/runtime.js +4 -5
  61. package/dist/spy.js +1 -1
  62. package/dist/task-utils.js +1 -2
  63. package/dist/worker.d.ts +5 -10
  64. package/dist/worker.js +24 -36
  65. package/dist/workers/forks.js +21 -33
  66. package/dist/workers/runVmTests.js +15 -20
  67. package/dist/workers/threads.js +21 -33
  68. package/dist/workers/vmForks.js +13 -15
  69. package/dist/workers/vmThreads.js +13 -15
  70. package/package.json +20 -20
  71. package/dist/chunks/_commonjsHelpers.D26ty3Ew.js +0 -6
  72. package/dist/chunks/coverage.d.Dn6XbyQq.d.ts +0 -43
  73. package/dist/chunks/globals.D2wDMk06.js +0 -33
  74. package/dist/chunks/index.6dZyLbho.js +0 -6918
  75. package/dist/chunks/nativeModuleRunner.WlMdOB52.js +0 -36
  76. package/dist/chunks/setup-common.DRlwpHsL.js +0 -64
@@ -1,22 +1,17 @@
1
- import { T as TestSyntaxError, g as getCurrentTest, c as createTaskCollector, a as getCurrentSuite, b as createChainable, d as getHooks, e as getFn, m as matchesTags, f as afterAll, h as afterEach, i as aroundAll, j as aroundEach, k as beforeAll, l as beforeEach, n as describe, o as it, q as onTestFailed, r as onTestFinished, t as recordArtifact, u as suite, v as test } from './artifact.C0OHWdod.js';
1
+ import { b as printDiffOrStringify, d as diff, a as processError, T as TestSyntaxError, g as getCurrentTest, c as createTaskCollector, e as getCurrentSuite, f as createChainable, h as getHooks, i as getFn, m as matchesTags, j as afterAll, k as afterEach, l as aroundAll, n as aroundEach, o as beforeAll, q as beforeEach, r as describe, t as it, u as onTestFailed, v as onTestFinished, w as recordArtifact, x as suite, y as test } from './artifact.DC5WoEgy.js';
2
2
  import { i as isChildProcess, r as resetModules, g as getWorkerState } from './utils.DYj33du9.js';
3
- import { getSafeTimers, delay } from '@vitest/utils/timers';
4
- import { isMockFunction, fn, spyOn, restoreAllMocks, resetAllMocks, clearAllMocks } from '@vitest/spy';
5
- import { getType, isObject, noop, assertTypes, ordinal, createSimpleStackTrace, getCallLastIndex } from '@vitest/utils/helpers';
6
- import { positionToOffset, offsetToLineNumber, lineSplitRE } from '@vitest/utils/offset';
7
- import { parseSingleStack, parseErrorStacktrace } from '@vitest/utils/source-map';
8
- import { c as commonjsGlobal, g as getDefaultExportFromCjs } from './_commonjsHelpers.D26ty3Ew.js';
9
- import { R as RealDate, b as resetDate, m as mockDate, r as rpc, V as VitestEvaluatedModules } from './rpc.DZEh5xnQ.js';
3
+ import { h as commonjsGlobal, a as getSafeTimers, f as parseSingleStack, i as delay, d as getDefaultExportFromCjs, j as parseErrorStacktrace } from './source-map.DZdIqpIJ.js';
4
+ import { i as isMockFunction, f as fn, a as spyOn, r as restoreAllMocks, b as resetAllMocks, c as clearAllMocks } from './spy.B8mHA9qu.js';
5
+ import { g as getType, v as isObject, p as noop, y as assertTypes, x as ordinal, D as createSimpleStackTrace, E as getCallLastIndex, i as isAbsolute, a as relative, n as normalize } from './pathe.M-eThtNZ.CbK_Vbae.js';
6
+ import { R as RealDate, b as resetDate, m as mockDate, r as rpc, V as VitestEvaluatedModules } from './rpc.iNjF664v.js';
7
+ import { s as stringify, i as inspect } from './display.CYwyMF4S.js';
8
+ import { y } from './tinyrainbow.Ht9iggcq.js';
10
9
  import * as chai from 'chai';
11
10
  import { use, util } from 'chai';
12
11
  import { getNames, getTests, getTestName, createFileTask } from '../task-utils.js';
13
- import { processError } from '@vitest/utils/error';
14
- import { g as getSerializers, a as addSerializer } from './plugins.DrsmdUE2.js';
15
- import { format } from '@vitest/pretty-format';
16
- import { printDiffOrStringify, diff } from '@vitest/utils/diff';
17
- import { stringify, inspect } from '@vitest/utils/display';
18
- import c from 'tinyrainbow';
19
- import { isAbsolute, relative, normalize } from 'pathe';
12
+ import { g as getSerializers, a as addSerializer } from './plugins.CsoX-42X.js';
13
+ import { p as positionToOffset, o as offsetToLineNumber, l as lineSplitRE } from './offset.Dy-5Fdfn.js';
14
+ import { f as format } from './index.DzTmMrSM.js';
20
15
  import { Bench } from 'tinybench';
21
16
  import { expectTypeOf } from 'expect-type';
22
17
 
@@ -119,11 +114,11 @@ ${printReceived(actual)}`
119
114
  }
120
115
  };
121
116
 
122
- const EXPECTED_COLOR = c.green;
123
- const RECEIVED_COLOR = c.red;
124
- const INVERTED_COLOR = c.inverse;
125
- const BOLD_WEIGHT = c.bold;
126
- const DIM_COLOR = c.dim;
117
+ const EXPECTED_COLOR = y.green;
118
+ const RECEIVED_COLOR = y.red;
119
+ const INVERTED_COLOR = y.inverse;
120
+ const BOLD_WEIGHT = y.bold;
121
+ const DIM_COLOR = y.dim;
127
122
  function matcherHint(matcherName, received = "received", expected = "expected", options = {}) {
128
123
  const { comment = "", isDirectExpectCall = false, isNot = false, promise = "", secondArgument = "", expectedColor = EXPECTED_COLOR, receivedColor = RECEIVED_COLOR, secondArgumentColor = EXPECTED_COLOR } = options;
129
124
  let hint = "";
@@ -1449,25 +1444,25 @@ const JestChaiExpect = (chai, utils) => {
1449
1444
  });
1450
1445
  };
1451
1446
  function formatCalls(spy, msg, showActualCall) {
1452
- if (spy.mock.calls.length) msg += c.gray(`\n\nReceived:\n\n${spy.mock.calls.map((callArg, i) => {
1453
- let methodCall = c.bold(` ${ordinal(i + 1)} ${spy.getMockName()} call:\n\n`);
1447
+ if (spy.mock.calls.length) msg += y.gray(`\n\nReceived:\n\n${spy.mock.calls.map((callArg, i) => {
1448
+ let methodCall = y.bold(` ${ordinal(i + 1)} ${spy.getMockName()} call:\n\n`);
1454
1449
  if (showActualCall) methodCall += diff(showActualCall, callArg, { omitAnnotationLines: true });
1455
1450
  else methodCall += stringify(callArg).split("\n").map((line) => ` ${line}`).join("\n");
1456
1451
  methodCall += "\n";
1457
1452
  return methodCall;
1458
1453
  }).join("\n")}`);
1459
- msg += c.gray(`\n\nNumber of calls: ${c.bold(spy.mock.calls.length)}\n`);
1454
+ msg += y.gray(`\n\nNumber of calls: ${y.bold(spy.mock.calls.length)}\n`);
1460
1455
  return msg;
1461
1456
  }
1462
1457
  function formatReturns(spy, results, msg, showActualReturn) {
1463
- if (results.length) msg += c.gray(`\n\nReceived:\n\n${results.map((callReturn, i) => {
1464
- let methodCall = c.bold(` ${ordinal(i + 1)} ${spy.getMockName()} call return:\n\n`);
1458
+ if (results.length) msg += y.gray(`\n\nReceived:\n\n${results.map((callReturn, i) => {
1459
+ let methodCall = y.bold(` ${ordinal(i + 1)} ${spy.getMockName()} call return:\n\n`);
1465
1460
  if (showActualReturn) methodCall += diff(showActualReturn, callReturn.value, { omitAnnotationLines: true });
1466
1461
  else methodCall += stringify(callReturn).split("\n").map((line) => ` ${line}`).join("\n");
1467
1462
  methodCall += "\n";
1468
1463
  return methodCall;
1469
1464
  }).join("\n")}`);
1470
- msg += c.gray(`\n\nNumber of calls: ${c.bold(spy.mock.calls.length)}\n`);
1465
+ msg += y.gray(`\n\nNumber of calls: ${y.bold(spy.mock.calls.length)}\n`);
1471
1466
  return msg;
1472
1467
  }
1473
1468
 
@@ -2729,15 +2724,35 @@ function requireFakeTimersSrc () {
2729
2724
  * @returns {void}
2730
2725
  */
2731
2726
 
2727
+ /**
2728
+ * @typedef {object} TemporalDuration
2729
+ * @property {number} years - years component
2730
+ * @property {number} months - months component
2731
+ * @property {number} weeks - weeks component
2732
+ * @property {number} days - days component
2733
+ * @property {number} hours - hours component
2734
+ * @property {number} minutes - minutes component
2735
+ * @property {number} seconds - seconds component
2736
+ * @property {number} milliseconds - milliseconds component
2737
+ * @property {number} microseconds - microseconds component
2738
+ * @property {number} nanoseconds - nanoseconds component
2739
+ * @property {(options: {unit: string, relativeTo?: unknown}) => number} total - converts to a single unit
2740
+ */
2741
+
2742
+ /**
2743
+ * @typedef {object} TemporalTimelike
2744
+ * @property {number} epochMilliseconds - milliseconds since the Unix epoch (present on Temporal.Instant and Temporal.ZonedDateTime)
2745
+ */
2746
+
2732
2747
  /**
2733
2748
  * @callback Tick
2734
- * @param {number|string} tickValue milliseconds or a string parseable by parseTime
2749
+ * @param {number|string|TemporalDuration} tickValue milliseconds, a string parseable by parseTime, or a Temporal.Duration
2735
2750
  * @returns {number} will return the new `now` value
2736
2751
  */
2737
2752
 
2738
2753
  /**
2739
2754
  * @callback TickAsync
2740
- * @param {number|string} tickValue milliseconds or a string parseable by parseTime
2755
+ * @param {number|string|TemporalDuration} tickValue milliseconds, a string parseable by parseTime, or a Temporal.Duration
2741
2756
  * @returns {Promise<number>}
2742
2757
  */
2743
2758
 
@@ -2783,13 +2798,13 @@ function requireFakeTimersSrc () {
2783
2798
 
2784
2799
  /**
2785
2800
  * @callback SetSystemTime
2786
- * @param {number|Date} [now] initial mocked time, as milliseconds since epoch or a Date
2801
+ * @param {number|Date|TemporalTimelike} [now] initial mocked time, as milliseconds since epoch, a Date, a Temporal.Instant, or a Temporal.ZonedDateTime
2787
2802
  * @returns {void}
2788
2803
  */
2789
2804
 
2790
2805
  /**
2791
2806
  * @callback Jump
2792
- * @param {number|string} tickValue milliseconds or a human-readable value like "01:11:15"
2807
+ * @param {number|string|TemporalDuration} tickValue milliseconds, a human-readable value like "01:11:15", or a Temporal.Duration
2793
2808
  * @returns {number}
2794
2809
  */
2795
2810
 
@@ -2817,7 +2832,7 @@ function requireFakeTimersSrc () {
2817
2832
  */
2818
2833
 
2819
2834
  /**
2820
- * @typedef {"setTimeout" | "clearTimeout" | "setImmediate" | "clearImmediate" | "setInterval" | "clearInterval" | "Date" | "nextTick" | "hrtime" | "requestAnimationFrame" | "cancelAnimationFrame" | "requestIdleCallback" | "cancelIdleCallback" | "performance" | "queueMicrotask"} FakeMethod
2835
+ * @typedef {"setTimeout" | "clearTimeout" | "setImmediate" | "clearImmediate" | "setInterval" | "clearInterval" | "Date" | "nextTick" | "hrtime" | "requestAnimationFrame" | "cancelAnimationFrame" | "requestIdleCallback" | "cancelIdleCallback" | "performance" | "queueMicrotask" | "Intl" | "Temporal"} FakeMethod
2821
2836
  */
2822
2837
 
2823
2838
  /**
@@ -2842,6 +2857,7 @@ function requireFakeTimersSrc () {
2842
2857
  * performance?: any,
2843
2858
  * Performance?: any,
2844
2859
  * Intl?: any,
2860
+ * Temporal?: any,
2845
2861
  * Promise?: typeof Promise,
2846
2862
  * Date: typeof Date & { isFake?: boolean, toSource?: () => string, clock?: any }
2847
2863
  * }} GlobalObject
@@ -2873,6 +2889,10 @@ function requireFakeTimersSrc () {
2873
2889
  * @typedef {Record<string, any> & { clock: Clock }} IntlWithClock
2874
2890
  */
2875
2891
 
2892
+ /**
2893
+ * @typedef {Record<string, any> & { now: () => number }} PerformanceLike
2894
+ */
2895
+
2876
2896
  /**
2877
2897
  * @typedef {object} Timers
2878
2898
  * @property {SetTimeout} setTimeout - native `setTimeout`
@@ -2881,11 +2901,12 @@ function requireFakeTimersSrc () {
2881
2901
  * @property {ClearInterval} clearInterval - native `clearInterval`
2882
2902
  * @property {typeof Date} Date - native `Date`
2883
2903
  * @property {typeof Intl} [Intl] - native `Intl`
2904
+ * @property {any} [Temporal] - native `Temporal`
2884
2905
  * @property {SetImmediate} [setImmediate] - native `setImmediate`, if available
2885
2906
  * @property {ClearImmediate} [clearImmediate] - native `clearImmediate`, if available
2886
2907
  * @property {Hrtime} [hrtime] - native `process.hrtime`, if available
2887
2908
  * @property {NextTick} [nextTick] - native `process.nextTick`, if available
2888
- * @property {Performance} [performance] - native `performance`, if available
2909
+ * @property {PerformanceLike} [performance] - native `performance`, if available
2889
2910
  * @property {RequestAnimationFrame} [requestAnimationFrame] - native `requestAnimationFrame`, if available
2890
2911
  * @property {QueueMicrotask} [queueMicrotask] - whether `queueMicrotask` exists
2891
2912
  * @property {CancelAnimationFrame} [cancelAnimationFrame] - native `cancelAnimationFrame`, if available
@@ -2926,7 +2947,7 @@ function requireFakeTimersSrc () {
2926
2947
 
2927
2948
  /**
2928
2949
  * @callback CreateClockCallback
2929
- * @param {number|Date} [start] initial mocked time, as milliseconds since epoch or a Date
2950
+ * @param {number|Date|TemporalTimelike} [start] initial mocked time, as milliseconds since epoch, a Date, a Temporal.Instant, or a Temporal.ZonedDateTime
2930
2951
  * @param {number} [loopLimit] maximum number of timers run before aborting with an infinite-loop error
2931
2952
  * @returns {Clock}
2932
2953
  */
@@ -2993,13 +3014,14 @@ function requireFakeTimersSrc () {
2993
3014
  * @property {ClockTickMode} [tickMode] - internal flag
2994
3015
  * @property {Timer[]} [jobs] - internal flag
2995
3016
  * @property {IntlWithClock} [Intl] - fake Intl object
3017
+ * @property {any} [Temporal] - fake Temporal object
2996
3018
  */
2997
3019
  /* eslint-enable jsdoc/reject-any-type */
2998
3020
 
2999
3021
  /**
3000
3022
  * Configuration object for the `install` method.
3001
3023
  * @typedef {object} Config
3002
- * @property {number|Date} [now] initial mocked time, as milliseconds since epoch or a Date
3024
+ * @property {number|Date|TemporalTimelike} [now] initial mocked time, as milliseconds since epoch, a Date, a Temporal.Instant, or a Temporal.ZonedDateTime
3003
3025
  * @property {FakeMethod[]} [toFake] method names that should be faked
3004
3026
  * @property {FakeMethod[]} [toNotFake] method names that should remain native
3005
3027
  * @property {number} [loopLimit] maximum number of timers run before aborting with an infinite-loop error
@@ -3108,6 +3130,11 @@ function requireFakeTimersSrc () {
3108
3130
  isPresent.clearImmediate =
3109
3131
  _global.clearImmediate && typeof _global.clearImmediate === "function";
3110
3132
  isPresent.Intl = _global.Intl && typeof _global.Intl === "object";
3133
+ isPresent.Temporal =
3134
+ _global.Temporal !== null &&
3135
+ typeof _global.Temporal === "object" &&
3136
+ typeof _global.Temporal.Now !== "undefined" &&
3137
+ typeof _global.Temporal.Instant !== "undefined";
3111
3138
 
3112
3139
  if (_global.clearTimeout) {
3113
3140
  _global.clearTimeout(timeoutResult);
@@ -3120,6 +3147,7 @@ function requireFakeTimersSrc () {
3120
3147
  Object.getOwnPropertyDescriptors(_global.Intl),
3121
3148
  )
3122
3149
  : undefined;
3150
+ const NativeTemporal = isPresent.Temporal ? _global.Temporal : undefined;
3123
3151
  let uniqueTimerId = idCounterStart;
3124
3152
  /** @type {number} */
3125
3153
  let uniqueTimerOrder = 0;
@@ -3237,7 +3265,7 @@ function requireFakeTimersSrc () {
3237
3265
 
3238
3266
  /**
3239
3267
  * Used to grok the `now` parameter to createClock.
3240
- * @param {Date|number} epoch the system time
3268
+ * @param {Date|number|TemporalTimelike} epoch the system time
3241
3269
  * @returns {number}
3242
3270
  */
3243
3271
  function getEpoch(epoch) {
@@ -3247,8 +3275,16 @@ function requireFakeTimersSrc () {
3247
3275
  if (typeof epoch === "number") {
3248
3276
  return epoch;
3249
3277
  }
3250
- if (typeof epoch.getTime === "function") {
3251
- return epoch.getTime();
3278
+ if (typeof (/** @type {Date} */ (epoch).getTime) === "function") {
3279
+ return /** @type {Date} */ (epoch).getTime();
3280
+ }
3281
+ if (
3282
+ typeof (
3283
+ /** @type {TemporalTimelike} */ (epoch).epochMilliseconds
3284
+ ) === "number"
3285
+ ) {
3286
+ // Temporal.Instant and Temporal.ZonedDateTime both have epochMilliseconds
3287
+ return /** @type {TemporalTimelike} */ (epoch).epochMilliseconds;
3252
3288
  }
3253
3289
  throw new TypeError("now should be milliseconds since UNIX epoch");
3254
3290
  }
@@ -3465,6 +3501,56 @@ function requireFakeTimersSrc () {
3465
3501
  return IntlWithClock;
3466
3502
  }
3467
3503
 
3504
+ //eslint-disable-next-line jsdoc/require-jsdoc
3505
+ function createTemporal(clock, getNanos) {
3506
+ const fakeNow = {
3507
+ instant() {
3508
+ return NativeTemporal.Instant.fromEpochNanoseconds(
3509
+ BigInt(clock.now) * 1_000_000n + BigInt(getNanos()),
3510
+ );
3511
+ },
3512
+ timeZoneId() {
3513
+ return NativeTemporal.Now.timeZoneId();
3514
+ },
3515
+ zonedDateTimeISO(timeZone) {
3516
+ const tz = timeZone ?? NativeTemporal.Now.timeZoneId();
3517
+ return fakeNow.instant().toZonedDateTimeISO(tz);
3518
+ },
3519
+ plainDateTimeISO(timeZone) {
3520
+ return fakeNow.zonedDateTimeISO(timeZone).toPlainDateTime();
3521
+ },
3522
+ plainDateISO(timeZone) {
3523
+ return fakeNow.zonedDateTimeISO(timeZone).toPlainDate();
3524
+ },
3525
+ plainTimeISO(timeZone) {
3526
+ return fakeNow.zonedDateTimeISO(timeZone).toPlainTime();
3527
+ },
3528
+ };
3529
+
3530
+ const TemporalWithClock = Object.create(
3531
+ Object.getPrototypeOf(NativeTemporal),
3532
+ );
3533
+ [
3534
+ ...Object.getOwnPropertyNames(NativeTemporal),
3535
+ ...Object.getOwnPropertySymbols(NativeTemporal),
3536
+ ].forEach((prop) => {
3537
+ Object.defineProperty(
3538
+ TemporalWithClock,
3539
+ prop,
3540
+ Object.getOwnPropertyDescriptor(NativeTemporal, prop),
3541
+ );
3542
+ });
3543
+ // Temporal.Now is writable:false in the spec so we must use defineProperty
3544
+ Object.defineProperty(TemporalWithClock, "Now", {
3545
+ value: fakeNow,
3546
+ writable: true,
3547
+ enumerable: false,
3548
+ configurable: true,
3549
+ });
3550
+
3551
+ return TemporalWithClock;
3552
+ }
3553
+
3468
3554
  //eslint-disable-next-line jsdoc/require-jsdoc
3469
3555
  function enqueueJob(clock, job) {
3470
3556
  // enqueues a microtick-deferred task - ecma262/#sec-enqueuejob
@@ -4099,85 +4185,6 @@ function requireFakeTimersSrc () {
4099
4185
  }
4100
4186
  }
4101
4187
 
4102
- /**
4103
- * @param {Clock} clock
4104
- * @returns {Timer[]}
4105
- */
4106
- function uninstall(clock) {
4107
- let method, i, l;
4108
- const installedHrTime = "_hrtime";
4109
- const installedNextTick = "_nextTick";
4110
-
4111
- for (i = 0, l = clock.methods.length; i < l; i++) {
4112
- method = clock.methods[i];
4113
- if (method === "hrtime" && _global.process) {
4114
- _global.process.hrtime = clock[installedHrTime];
4115
- } else if (method === "nextTick" && _global.process) {
4116
- _global.process.nextTick = clock[installedNextTick];
4117
- } else if (method === "performance") {
4118
- const originalPerfDescriptor = Object.getOwnPropertyDescriptor(
4119
- clock,
4120
- `_${method}`,
4121
- );
4122
- if (
4123
- originalPerfDescriptor &&
4124
- originalPerfDescriptor.get &&
4125
- !originalPerfDescriptor.set
4126
- ) {
4127
- Object.defineProperty(
4128
- _global,
4129
- method,
4130
- originalPerfDescriptor,
4131
- );
4132
- } else if (originalPerfDescriptor.configurable) {
4133
- _global[method] = clock[`_${method}`];
4134
- }
4135
- } else {
4136
- if (clock[method] && clock[method].hasOwnProperty) {
4137
- _global[method] = clock[`_${method}`];
4138
- } else {
4139
- try {
4140
- delete _global[method];
4141
- } catch {
4142
- /* eslint no-empty: "off" */
4143
- }
4144
- }
4145
- }
4146
- if (clock.timersModuleMethods !== undefined) {
4147
- for (let j = 0; j < clock.timersModuleMethods.length; j++) {
4148
- const entry = clock.timersModuleMethods[j];
4149
- timersModule[entry.methodName] = entry.original;
4150
- }
4151
- }
4152
- if (clock.timersPromisesModuleMethods !== undefined) {
4153
- for (
4154
- let j = 0;
4155
- j < clock.timersPromisesModuleMethods.length;
4156
- j++
4157
- ) {
4158
- const entry = clock.timersPromisesModuleMethods[j];
4159
- timersPromisesModule[entry.methodName] = entry.original;
4160
- }
4161
- }
4162
- }
4163
-
4164
- clock.setTickMode({ mode: "manual" });
4165
-
4166
- // Prevent multiple executions which will completely remove these props
4167
- clock.methods = [];
4168
-
4169
- for (const [listener, signal] of clock.abortListenerMap.entries()) {
4170
- signal.removeEventListener("abort", listener);
4171
- clock.abortListenerMap.delete(listener);
4172
- }
4173
-
4174
- // return pending timers, to enable checking what timers remained on uninstall
4175
- if (!clock.timerHeap) {
4176
- return [];
4177
- }
4178
- return clock.timerHeap.timers.slice();
4179
- }
4180
-
4181
4188
  /**
4182
4189
  * @param {object} target the target containing the method to replace
4183
4190
  * @param {string} method the keyname of the method on the target
@@ -4194,6 +4201,8 @@ function requireFakeTimersSrc () {
4194
4201
  target[method] = clock[method];
4195
4202
  } else if (method === "Intl") {
4196
4203
  target[method] = clock[method];
4204
+ } else if (method === "Temporal") {
4205
+ target[method] = clock[method];
4197
4206
  } else if (method === "performance") {
4198
4207
  const originalPerfDescriptor = Object.getOwnPropertyDescriptor(
4199
4208
  target,
@@ -4294,12 +4303,16 @@ function requireFakeTimersSrc () {
4294
4303
  timers.Intl = NativeIntl;
4295
4304
  }
4296
4305
 
4306
+ if (isPresent.Temporal) {
4307
+ timers.Temporal = NativeTemporal;
4308
+ }
4309
+
4297
4310
  const originalSetTimeout = _global.setImmediate || _global.setTimeout;
4298
4311
  const originalClearInterval = _global.clearInterval;
4299
4312
  const originalSetInterval = _global.setInterval;
4300
4313
 
4301
4314
  /**
4302
- * @param {Date|number} [start] the system time - non-integer values are floored
4315
+ * @param {Date|number|TemporalTimelike} [start] the system time - non-integer values are floored
4303
4316
  * @param {number} [loopLimit] maximum number of timers that will be run when calling runAll()
4304
4317
  * @returns {Clock}
4305
4318
  */
@@ -4312,6 +4325,7 @@ function requireFakeTimersSrc () {
4312
4325
  loopLimit = loopLimit || 1000;
4313
4326
  /** @type {number} */
4314
4327
  let nanos = 0;
4328
+ let uninstalled = false;
4315
4329
  /** @type {number[]} */
4316
4330
  const adjustedSystemTime = [0, 0]; // [millis, nanoremainder]
4317
4331
 
@@ -4389,6 +4403,10 @@ function requireFakeTimersSrc () {
4389
4403
  clock.Intl.clock = clock;
4390
4404
  }
4391
4405
 
4406
+ if (isPresent.Temporal) {
4407
+ clock.Temporal = createTemporal(clock, () => nanos);
4408
+ }
4409
+
4392
4410
  /**
4393
4411
  * @param {SetTickModeConfig} tickModeConfig - The new configuration for how the clock should tick.
4394
4412
  */
@@ -4467,7 +4485,9 @@ function requireFakeTimersSrc () {
4467
4485
  }
4468
4486
  clock.setTickMode({ mode: "manual" });
4469
4487
  return promise.finally(() => {
4470
- clock.setTickMode({ mode: "nextAsync" });
4488
+ if (!uninstalled) {
4489
+ clock.setTickMode({ mode: "nextAsync" });
4490
+ }
4471
4491
  });
4472
4492
  }
4473
4493
 
@@ -4623,15 +4643,44 @@ function requireFakeTimersSrc () {
4623
4643
  runJobs(clock);
4624
4644
  };
4625
4645
 
4646
+ //eslint-disable-next-line jsdoc/require-jsdoc
4647
+ function durationToMs(duration) {
4648
+ // relativeTo uses the real system timezone — fake-timers fakes time, not place.
4649
+ // Calendar-unit durations (months, years) will resolve DST/length using the host tz.
4650
+ const relativeTo = NativeTemporal.Instant.fromEpochMilliseconds(
4651
+ clock.now,
4652
+ ).toZonedDateTimeISO(NativeTemporal.Now.timeZoneId());
4653
+ return duration.total({ unit: "millisecond", relativeTo });
4654
+ }
4655
+
4656
+ /**
4657
+ * @param {number|string|TemporalDuration} tickValue
4658
+ * @returns {number} milliseconds as a float
4659
+ */
4660
+ function tickValueToMs(tickValue) {
4661
+ if (typeof tickValue === "number") {
4662
+ return tickValue;
4663
+ }
4664
+ if (
4665
+ isPresent.Temporal &&
4666
+ tickValue !== null &&
4667
+ typeof tickValue === "object" &&
4668
+ typeof (/** @type {TemporalDuration} */ (tickValue).total) ===
4669
+ "function"
4670
+ ) {
4671
+ return durationToMs(
4672
+ /** @type {TemporalDuration} */ (tickValue),
4673
+ );
4674
+ }
4675
+ return parseTime(/** @type {string} */ (tickValue));
4676
+ }
4677
+
4626
4678
  /**
4627
- * @param {number|string} tickValue milliseconds or a string parseable by parseTime
4679
+ * @param {number|string|TemporalDuration} tickValue milliseconds, a string parseable by parseTime, or a Temporal.Duration
4628
4680
  * @returns {ClockState} a mutable state object for the tick execution
4629
4681
  */
4630
4682
  function createTickState(tickValue) {
4631
- const msFloat =
4632
- typeof tickValue === "number"
4633
- ? tickValue
4634
- : parseTime(tickValue);
4683
+ const msFloat = tickValueToMs(tickValue);
4635
4684
  const ms = Math.floor(msFloat);
4636
4685
  const remainder = nanoRemainder(msFloat);
4637
4686
  let nanosTotal = nanos + remainder;
@@ -4792,7 +4841,7 @@ function requireFakeTimersSrc () {
4792
4841
  }
4793
4842
 
4794
4843
  /**
4795
- * @param {number|string} tickValue milliseconds or a string parseable by parseTime
4844
+ * @param {number|string|TemporalDuration} tickValue milliseconds or a string parseable by parseTime
4796
4845
  * @param {boolean} isAsync whether this is an async tick
4797
4846
  * @param {FakeTimersFunction} [resolve] promise resolve function
4798
4847
  * @param {FakeTimersFunction} [reject] promise reject function
@@ -4848,7 +4897,7 @@ function requireFakeTimersSrc () {
4848
4897
  }
4849
4898
 
4850
4899
  /**
4851
- * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15"
4900
+ * @param {string|number|TemporalDuration} tickValue number of milliseconds, a human-readable value like "01:11:15", or a Temporal.Duration
4852
4901
  * @returns {number} will return the new `now` value
4853
4902
  */
4854
4903
  clock.tick = function tick(tickValue) {
@@ -4929,7 +4978,7 @@ function requireFakeTimersSrc () {
4929
4978
 
4930
4979
  if (typeof _global.Promise !== "undefined") {
4931
4980
  /**
4932
- * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15"
4981
+ * @param {string|number|TemporalDuration} tickValue number of milliseconds, a human-readable value like "01:11:15", or a Temporal.Duration
4933
4982
  * @returns {Promise}
4934
4983
  */
4935
4984
  clock.tickAsync = function tickAsync(tickValue) {
@@ -5055,14 +5104,11 @@ function requireFakeTimersSrc () {
5055
5104
  };
5056
5105
 
5057
5106
  /**
5058
- * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15"
5107
+ * @param {string|number|TemporalDuration} tickValue number of milliseconds, a human-readable value like "01:11:15", or a Temporal.Duration
5059
5108
  * @returns {number} the new `now` value
5060
5109
  */
5061
5110
  clock.jump = function jump(tickValue) {
5062
- const msFloat =
5063
- typeof tickValue === "number"
5064
- ? tickValue
5065
- : parseTime(tickValue);
5111
+ const msFloat = tickValueToMs(tickValue);
5066
5112
  const ms = Math.floor(msFloat);
5067
5113
 
5068
5114
  forEachActiveTimer(clock, (timer) => {
@@ -5087,6 +5133,97 @@ function requireFakeTimersSrc () {
5087
5133
  clock.hrtime = hrtime;
5088
5134
  }
5089
5135
 
5136
+ /**
5137
+ * @returns {Timer[]}
5138
+ */
5139
+ clock.uninstall = function () {
5140
+ uninstalled = true;
5141
+ clock.setTickMode({ mode: "manual" });
5142
+
5143
+ if (clock.methods) {
5144
+ const installedHrTime = "_hrtime";
5145
+ const installedNextTick = "_nextTick";
5146
+ let method, i, l;
5147
+ for (i = 0, l = clock.methods.length; i < l; i++) {
5148
+ method = clock.methods[i];
5149
+ if (method === "hrtime" && _global.process) {
5150
+ _global.process.hrtime = clock[installedHrTime];
5151
+ } else if (method === "nextTick" && _global.process) {
5152
+ _global.process.nextTick = clock[installedNextTick];
5153
+ } else if (method === "performance") {
5154
+ const originalPerfDescriptor =
5155
+ Object.getOwnPropertyDescriptor(
5156
+ clock,
5157
+ `_${method}`,
5158
+ );
5159
+ if (
5160
+ originalPerfDescriptor &&
5161
+ originalPerfDescriptor.get &&
5162
+ !originalPerfDescriptor.set
5163
+ ) {
5164
+ Object.defineProperty(
5165
+ _global,
5166
+ method,
5167
+ originalPerfDescriptor,
5168
+ );
5169
+ } else if (originalPerfDescriptor.configurable) {
5170
+ _global[method] = clock[`_${method}`];
5171
+ }
5172
+ } else {
5173
+ if (clock[method] && clock[method].hasOwnProperty) {
5174
+ _global[method] = clock[`_${method}`];
5175
+ } else {
5176
+ try {
5177
+ delete _global[method];
5178
+ } catch {
5179
+ /* eslint no-empty: "off" */
5180
+ }
5181
+ }
5182
+ }
5183
+ if (clock.timersModuleMethods !== undefined) {
5184
+ for (
5185
+ let j = 0;
5186
+ j < clock.timersModuleMethods.length;
5187
+ j++
5188
+ ) {
5189
+ const entry = clock.timersModuleMethods[j];
5190
+ timersModule[entry.methodName] = entry.original;
5191
+ }
5192
+ }
5193
+ if (clock.timersPromisesModuleMethods !== undefined) {
5194
+ for (
5195
+ let j = 0;
5196
+ j < clock.timersPromisesModuleMethods.length;
5197
+ j++
5198
+ ) {
5199
+ const entry = clock.timersPromisesModuleMethods[j];
5200
+ timersPromisesModule[entry.methodName] =
5201
+ entry.original;
5202
+ }
5203
+ }
5204
+ }
5205
+
5206
+ // Prevent multiple executions which will completely remove these props
5207
+ clock.methods = [];
5208
+ }
5209
+
5210
+ if (clock.abortListenerMap) {
5211
+ for (const [
5212
+ listener,
5213
+ signal,
5214
+ ] of clock.abortListenerMap.entries()) {
5215
+ signal.removeEventListener("abort", listener);
5216
+ clock.abortListenerMap.delete(listener);
5217
+ }
5218
+ }
5219
+
5220
+ // return pending timers, to enable checking what timers remained on uninstall
5221
+ if (!clock.timerHeap) {
5222
+ return [];
5223
+ }
5224
+ return clock.timerHeap.timers.slice();
5225
+ };
5226
+
5090
5227
  return clock;
5091
5228
  }
5092
5229
 
@@ -5175,10 +5312,6 @@ function requireFakeTimersSrc () {
5175
5312
  const clock = createClock(config.now, config.loopLimit);
5176
5313
  clock.shouldClearNativeTimers = config.shouldClearNativeTimers;
5177
5314
 
5178
- clock.uninstall = function () {
5179
- return uninstall(clock);
5180
- };
5181
-
5182
5315
  clock.abortListenerMap = new Map();
5183
5316
 
5184
5317
  if (hasToFake) {
@@ -5692,6 +5825,235 @@ class FakeTimers {
5692
5825
  }
5693
5826
  }
5694
5827
 
5828
+ const whenSymbol = Symbol.for("$$vitest:when");
5829
+ /**
5830
+ * Returns `true` if the given value is a {@linkcode When} chain created by {@linkcode when|vi.when}.
5831
+ *
5832
+ * @param input - The value to check.
5833
+ * @returns `true` if `input` is a {@linkcode When} instance, `false` otherwise.
5834
+ *
5835
+ * @example
5836
+ * const spy = vi.fn()
5837
+ * const w = vi.when(spy).calledWith(1).thenReturn(0)
5838
+ *
5839
+ * expect(isWhenChain(w)).toBe(true)
5840
+ * expect(isWhenChain(spy)).toBe(false)
5841
+ */
5842
+ function isWhenChain(input) {
5843
+ try {
5844
+ return Reflect.has(input, whenSymbol);
5845
+ } catch {
5846
+ return false;
5847
+ }
5848
+ }
5849
+ /**
5850
+ * Defines conditional behaviors on a Vitest spy based on the arguments it is called with.
5851
+ *
5852
+ * Behaviors are matched using deep equality, last-registered first within each argument set.
5853
+ *
5854
+ * It automatically restores the spy's original implementation when the enclosing block exits if used with the `using` keyword.
5855
+ *
5856
+ * @param spy - A Vitest mock function to attach behaviors to.
5857
+ * @param options - Optional configuration.
5858
+ * @returns A {@linkcode When} instance for registering behaviors.
5859
+ *
5860
+ * @throws {TypeError} If `spy` is not a Vitest mock function.
5861
+ * @throws {Error} If chaining a non-existent method after `calledWith`.
5862
+ * @throws {RangeError} If setting the `times` option on a `then*` method as a negative value or `0`.
5863
+ *
5864
+ * @since 5.0.0
5865
+ * @see {@link https://vitest.dev/api/vi#vi-when}
5866
+ * @see {@link https://vitest.dev/guide/recipes/conditional-mocking}
5867
+ *
5868
+ * @example
5869
+ * // Basic usage
5870
+ * const spy = vi.fn(() => Number.NEGATIVE_INFINITY)
5871
+ * vi.when(spy).calledWith(1).thenReturn(0)
5872
+ *
5873
+ * expect(spy(1)).toBe(0)
5874
+ * expect(spy(2)).toBe(Number.NEGATIVE_INFINITY) // falls through to original implementation
5875
+ *
5876
+ * @example
5877
+ * // Async
5878
+ * const spy = vi.fn()
5879
+ * vi.when(spy).calledWith('user').thenResolve({ id: 1 })
5880
+ *
5881
+ * await expect(spy('user')).resolves.toEqual({ id: 1 })
5882
+ *
5883
+ * @example
5884
+ * // Scoped with `using`
5885
+ * const spy = vi.fn()
5886
+ *
5887
+ * {
5888
+ * using w = vi.when(spy)
5889
+ * .calledWith('darkMode')
5890
+ * .thenReturn(true)
5891
+ *
5892
+ * expect(spy('darkMode')).toBe(true)
5893
+ * }
5894
+ *
5895
+ * // spy's original implementation is restored here
5896
+ * expect(spy('darkMode')).toBe(undefined)
5897
+ *
5898
+ * @example
5899
+ * // Throw on unmatched calls
5900
+ * vi.when(spy, { onUnmatched: 'throw' })
5901
+ * .calledWith(1)
5902
+ * .thenReturn({ id: 1, name: 'Alice' })
5903
+ *
5904
+ * expect(spy(1)).toEqual({ id: 1, name: 'Alice' })
5905
+ * expect(() => spy(2)).toThrow()
5906
+ */
5907
+ function when(spy, options) {
5908
+ if (!isMockFunction(spy)) throw new TypeError("vi.when: the argument must be a mock function created with `vi.fn()` or `vi.spyOn()`");
5909
+ const behaviors = [];
5910
+ const originalImplementation = spy.getMockImplementation();
5911
+ function findAction(args) {
5912
+ const testers = [...getCustomEqualityTesters(), iterableEquality];
5913
+ for (const behavior of behaviors) if (equals(args, behavior.arguments, testers)) return behavior.actions.findLast((action) => !(action.remaining === 0 && action.called)) ?? null;
5914
+ return null;
5915
+ }
5916
+ spy.mockImplementation(
5917
+ // @ts-expect-error cannot resolve generic args
5918
+ (...args) => {
5919
+ const action = findAction(args);
5920
+ if (action === null) return (typeof options?.onUnmatched === "function" ? options.onUnmatched : options?.onUnmatched === "throw" ? () => {
5921
+ throw new Error(`vi.when: no behavior defined when called with [${args.map((arg) => stringify(arg)).join(", ")}]`);
5922
+ } : originalImplementation)?.(...args);
5923
+ action.remaining -= 1;
5924
+ action.called = true;
5925
+ switch (action.type) {
5926
+ case "return": return action.value;
5927
+ case "throw": throw action.value;
5928
+ case "resolve": return Promise.resolve(action.value);
5929
+ case "reject": return Promise.reject(action.value);
5930
+ }
5931
+ }
5932
+ );
5933
+ function getOrCreateBehavior(args) {
5934
+ const testers = [...getCustomEqualityTesters(), iterableEquality];
5935
+ let behavior = behaviors.find((behavior) => equals(args, behavior.arguments, testers));
5936
+ if (behavior === void 0) {
5937
+ behavior = {
5938
+ arguments: args,
5939
+ actions: []
5940
+ };
5941
+ behaviors.push(behavior);
5942
+ }
5943
+ return behavior;
5944
+ }
5945
+ // @ts-expect-error `Symbol.dispose` has to be assigned conditionally since it's only supported in Node >= 24
5946
+ const output = markWhenChain({
5947
+ calledWith: (...args) => {
5948
+ const behavior = getOrCreateBehavior(args);
5949
+ function appendAction(behavior, type, value, times) {
5950
+ behavior.actions.push({
5951
+ type,
5952
+ value,
5953
+ times,
5954
+ remaining: times,
5955
+ called: false
5956
+ });
5957
+ }
5958
+ const calledWithInstance = markWhenChain({
5959
+ ...output,
5960
+ thenThrow: (value, options) => {
5961
+ validateOptions(options);
5962
+ appendAction(behavior, "throw", value, options?.times ?? Number.POSITIVE_INFINITY);
5963
+ return calledWithInstance;
5964
+ },
5965
+ thenThrowOnce: (value) => {
5966
+ appendAction(behavior, "throw", value, 1);
5967
+ return calledWithInstance;
5968
+ },
5969
+ thenReturn: (value, options) => {
5970
+ validateOptions(options);
5971
+ appendAction(behavior, "return", value, options?.times ?? Number.POSITIVE_INFINITY);
5972
+ return calledWithInstance;
5973
+ },
5974
+ thenReturnOnce: (value) => {
5975
+ appendAction(behavior, "return", value, 1);
5976
+ return calledWithInstance;
5977
+ },
5978
+ thenResolve: (value, options) => {
5979
+ validateOptions(options);
5980
+ appendAction(behavior, "resolve", value, options?.times ?? Number.POSITIVE_INFINITY);
5981
+ return calledWithInstance;
5982
+ },
5983
+ thenResolveOnce: (value) => {
5984
+ appendAction(behavior, "resolve", value, 1);
5985
+ return calledWithInstance;
5986
+ },
5987
+ thenReject: (value, options) => {
5988
+ validateOptions(options);
5989
+ appendAction(behavior, "reject", value, options?.times ?? Number.POSITIVE_INFINITY);
5990
+ return calledWithInstance;
5991
+ },
5992
+ thenRejectOnce: (value) => {
5993
+ appendAction(behavior, "reject", value, 1);
5994
+ return calledWithInstance;
5995
+ }
5996
+ });
5997
+ return calledWithInstance;
5998
+ },
5999
+ _getDiagnostics: () => {
6000
+ const pendingBehaviors = behaviors.filter((behavior) => behavior.actions.length === 0 || behavior.actions.some((action) => !hasBeenConsumed(action)));
6001
+ return {
6002
+ isExhausted: behaviors.length !== 0 && pendingBehaviors.length === 0,
6003
+ pendingBehaviors: pendingBehaviors.map((behavior) => `calledWith(${behavior.arguments.map((argument) => stringify(argument)).join(", ")})${behavior.actions.length === 0 ? " → no actions" : `\n${formatActions(behavior.actions)}`}`).join("\n\n")
6004
+ };
6005
+ }
6006
+ });
6007
+ if (Symbol.dispose) output[Symbol.dispose] = () => {
6008
+ spy.mockImplementation(
6009
+ // @ts-expect-error without an original implementation we should fall back to an undefined-returning function as that's what the mocking functions do
6010
+ originalImplementation ?? noop
6011
+ );
6012
+ };
6013
+ return output;
6014
+ }
6015
+ const whenChainMarkerOptions = {};
6016
+ function markWhenChain(input) {
6017
+ Reflect.defineProperty(input, whenSymbol, whenChainMarkerOptions);
6018
+ return input;
6019
+ }
6020
+ function formatActions(actions) {
6021
+ const lines = actions.map((action, index) => {
6022
+ const method = getMethodName(action.type);
6023
+ const left = ` ${getSymbol(action)} ${method}(${stringify(action.value)}${action.times === Number.POSITIVE_INFINITY ? "" : `, { times: ${action.times} }`})`;
6024
+ const unreachable = !hasBeenConsumed(action) && actions.slice(index + 1).some((later) => later.times === Number.POSITIVE_INFINITY);
6025
+ return {
6026
+ left,
6027
+ remaining: getRemainingLabel(action) + (unreachable ? " → unreachable action" : "")
6028
+ };
6029
+ });
6030
+ const maxLeft = Math.max(...lines.map((line) => line.left.length));
6031
+ return lines.map(({ left, remaining }) => `${left.padEnd(maxLeft + 2)}${remaining}`).join("\n");
6032
+ }
6033
+ function getMethodName(type) {
6034
+ switch (type) {
6035
+ case "return": return "thenReturn";
6036
+ case "resolve": return "thenResolve";
6037
+ case "throw": return "thenThrow";
6038
+ case "reject": return "thenReject";
6039
+ default: throw new Error(`vi.when: "${type}" is not a known method`);
6040
+ }
6041
+ }
6042
+ function hasBeenConsumed(action) {
6043
+ return action.remaining === 0 || action.remaining === Number.POSITIVE_INFINITY && action.called;
6044
+ }
6045
+ function getRemainingLabel(action) {
6046
+ if (hasBeenConsumed(action)) return action.remaining === Number.POSITIVE_INFINITY ? "exhausted" : `exhausted (${action.times} of ${action.times})`;
6047
+ return action.remaining === Number.POSITIVE_INFINITY ? "never called" : `${action.remaining} remaining (out of ${action.times})`;
6048
+ }
6049
+ function getSymbol(action) {
6050
+ if (hasBeenConsumed(action)) return "✓";
6051
+ return "✗";
6052
+ }
6053
+ function validateOptions(options) {
6054
+ if (typeof options?.times === "number" && options.times <= 0) throw new RangeError("vi.when: `times` option must be greater than 0");
6055
+ }
6056
+
5695
6057
  function copyStackTrace$2(target, source) {
5696
6058
  if (source.stack !== void 0) target.stack = source.stack.replace(source.message, target.message);
5697
6059
  return target;
@@ -5888,6 +6250,8 @@ function createVitest() {
5888
6250
  },
5889
6251
  spyOn,
5890
6252
  fn,
6253
+ when,
6254
+ isWhenChain,
5891
6255
  waitFor,
5892
6256
  waitUntil,
5893
6257
  defineHelper: (fn) => {
@@ -5977,8 +6341,8 @@ function createVitest() {
5977
6341
  stubEnv(name, value) {
5978
6342
  const env = state().metaEnv;
5979
6343
  if (!_stubsEnv.has(name)) _stubsEnv.set(name, env[name]);
5980
- if (_envBooleans.includes(name)) env[name] = value ? "1" : "";
5981
- else if (value === void 0) delete env[name];
6344
+ if (value === void 0) delete env[name];
6345
+ else if (_envBooleans.includes(name)) env[name] = value ? "1" : "";
5982
6346
  else env[name] = String(value);
5983
6347
  return utils;
5984
6348
  },
@@ -6071,7 +6435,7 @@ const unsupported = [
6071
6435
  * @throws Always throws the provided error with an amended stack trace
6072
6436
  */
6073
6437
  function throwWithCause(error, source) {
6074
- if (error.cause == null) error.cause = /* @__PURE__ */ new Error("Matcher did not succeed in time.");
6438
+ error.cause ??= /* @__PURE__ */ new Error("Matcher did not succeed in time.");
6075
6439
  throw copyStackTrace(error, source);
6076
6440
  }
6077
6441
  function createExpectPoll(expect) {
@@ -6229,6 +6593,15 @@ function raceWith$1(promise, other) {
6229
6593
  }))]);
6230
6594
  }
6231
6595
 
6596
+ const MockPlugin = (chai, utils) => {
6597
+ utils.addMethod(chai.Assertion.prototype, "toHaveBeenExhausted", wrapAssertion(utils, "toHaveBeenExhausted", function() {
6598
+ const chain = utils.flag(this, "object");
6599
+ if (!isWhenChain(chain)) throw new TypeError(`${utils.inspect(chain)} is not a \`vi.when\` instance`);
6600
+ const diagnostics = chain._getDiagnostics();
6601
+ this.assert(diagnostics.isExhausted, `expected all behaviors to have been exhausted, but some remain:\n\n ${diagnostics.pendingBehaviors.replaceAll(/\n(?!\n)/g, "\n ")}`, "expected at least one behavior to remain un-exhausted, but all were");
6602
+ }));
6603
+ };
6604
+
6232
6605
  var naturalCompare$1 = {exports: {}};
6233
6606
 
6234
6607
  var hasRequiredNaturalCompare;
@@ -6888,7 +7261,7 @@ class SnapshotState {
6888
7261
  if (!rawSnapshot) receivedSerialized = addExtraLineBreaks(receivedSerialized);
6889
7262
  if (rawSnapshot) {
6890
7263
  // normalize EOL when snapshot contains CRLF but received is LF
6891
- if (rawSnapshot.content && rawSnapshot.content.match(/\r\n/) && !receivedSerialized.match(/\r\n/)) rawSnapshot.content = normalizeNewlines(rawSnapshot.content);
7264
+ if (rawSnapshot.content && /\r\n/.test(rawSnapshot.content) && !/\r\n/.test(receivedSerialized)) rawSnapshot.content = normalizeNewlines(rawSnapshot.content);
6892
7265
  }
6893
7266
  const expected = isInline ? inlineSnapshot : rawSnapshot ? rawSnapshot.content : this._snapshotData[key];
6894
7267
  const expectedTrimmed = rawSnapshot ? expected : expected?.trim();
@@ -7481,6 +7854,7 @@ chai.use(JestChaiExpect);
7481
7854
  chai.use(ChaiStyleAssertions);
7482
7855
  chai.use(SnapshotPlugin);
7483
7856
  chai.use(JestAsymmetricMatchers);
7857
+ chai.use(MockPlugin);
7484
7858
 
7485
7859
  function createExpect(test) {
7486
7860
  const expect = ((value, message) => {
@@ -7563,30 +7937,74 @@ function inject(key) {
7563
7937
  }
7564
7938
 
7565
7939
  const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
7940
+ /**
7941
+ * The built-in benchmark provider, backed by tinybench. Selected when
7942
+ * `benchmark.provider` is not configured.
7943
+ */
7944
+ function createDefaultBenchmarkProvider(config) {
7945
+ let benchIdx = 0;
7946
+ return { async run({ test, options, registrations }) {
7947
+ const currentIndex = ++benchIdx;
7948
+ const tinybench = new Bench({
7949
+ signal: test.context.signal,
7950
+ name: `${test.fullTestName} ${currentIndex}`,
7951
+ retainSamples: config.benchmark.retainSamples,
7952
+ ...options,
7953
+ now
7954
+ });
7955
+ for (const { name, fn, fnOpts } of registrations) tinybench.add(name, fn, fnOpts);
7956
+ await tinybench.run();
7957
+ const errors = tinybench.tasks.filter((task) => task.result.state === "errored").map((task) => task.result.error);
7958
+ if (errors.length === 1) throw errors[0];
7959
+ if (errors.length > 1) throw new AggregateError(errors, "Some benchmarks failed");
7960
+ return tinybench.tasks.map(toBenchResult);
7961
+ } };
7962
+ }
7963
+ function toBenchResult(task) {
7964
+ const result = task.result;
7965
+ if (result.state !== "completed") throw new Error(`task "${task.name}" did not complete: received "${result.state}"`);
7966
+ return {
7967
+ ...result,
7968
+ name: task.name
7969
+ };
7970
+ }
7971
+
7566
7972
  const kRegistration = Symbol("registration");
7567
7973
  const kFromSource = Symbol("fromSource");
7568
7974
  const kPerProject = Symbol("perProject");
7569
7975
  const kWriteResult = Symbol("writeResult");
7570
7976
  const kFinalize = Symbol("finalize");
7977
+ let cachedProvider;
7978
+ async function loadProviderModule(provider, moduleRunner) {
7979
+ let mod;
7980
+ try {
7981
+ mod = await moduleRunner.import(provider);
7982
+ } catch (error) {
7983
+ throw new Error(`Failed to load benchmark provider from "${provider}".`, { cause: error });
7984
+ }
7985
+ if (mod.default == null) throw new Error(`Benchmark provider loaded from "${provider}" did not have a default export.`);
7986
+ return mod.default;
7987
+ }
7988
+ /**
7989
+ * Resolves the benchmark provider for the current worker, importing a custom
7990
+ * provider module on first use. The result is cached for the lifetime of the
7991
+ * worker so a custom provider is imported at most once.
7992
+ */
7993
+ function resolveBenchmarkProvider(config, moduleRunner) {
7994
+ if (!cachedProvider) {
7995
+ const provider = config.benchmark.provider;
7996
+ cachedProvider = !provider ? Promise.resolve(createDefaultBenchmarkProvider(config)) : loadProviderModule(provider, moduleRunner);
7997
+ }
7998
+ return cachedProvider;
7999
+ }
7571
8000
  function isFromRegistration(reg) {
7572
8001
  return kFromSource in reg;
7573
8002
  }
7574
8003
  function substitutePath(template, projectName) {
7575
8004
  return template.replace(/\$\{projectName\}/g, projectName ?? "");
7576
8005
  }
7577
- function createBench(test, config) {
7578
- let benchIdx = 0;
8006
+ function createBench(test, config, moduleRunner) {
7579
8007
  const pending = /* @__PURE__ */ new Set();
7580
- const createTinybench = (options) => {
7581
- const currentIndex = ++benchIdx;
7582
- return new Bench({
7583
- signal: test.context.signal,
7584
- name: `${test.fullTestName} ${currentIndex}`,
7585
- retainSamples: config.benchmark.retainSamples,
7586
- ...options,
7587
- now
7588
- });
7589
- };
7590
8008
  const resolveTemplate = (template) => substitutePath(template, config.benchmark.projectName);
7591
8009
  const resolveFromSource = async (source) => {
7592
8010
  if (typeof source === "function") return source();
@@ -7604,42 +8022,37 @@ function createBench(test, config) {
7604
8022
  rank: 0,
7605
8023
  fromStore: true
7606
8024
  });
7607
- const createCompareStorage = (bench, fromResults) => {
8025
+ const createCompareStorage = (results, fromResults) => {
7608
8026
  return { get(name) {
7609
8027
  const stored = fromResults?.get(name);
7610
8028
  if (stored) return stored;
7611
- const task = bench.getTask(name);
7612
- if (!task) throw new Error(`task "${name}" was not defined`);
7613
- return task.result;
8029
+ const result = results.get(name);
8030
+ if (!result) throw new Error(`task "${name}" was not defined`);
8031
+ return result;
7614
8032
  } };
7615
8033
  };
7616
- const serializeBenchmark = (tinybenchTasks, name, taskMeta, fromTasks) => {
7617
- const tasks = tinybenchTasks.map((t) => {
7618
- const result = t.result;
7619
- if (result.state === "errored") throw result.error;
7620
- if (result.state !== "completed") throw new Error(`task "${t.name}" did not complete: received "${result.state}"`);
7621
- return {
7622
- name: t.name,
7623
- latency: result.latency,
7624
- throughput: result.throughput,
7625
- period: result.period,
7626
- totalTime: result.totalTime,
7627
- rank: 0,
7628
- ...taskMeta?.get(t.name)
7629
- };
7630
- });
8034
+ const serializeBenchmark = (results, name, taskMeta, fromTasks) => {
8035
+ const tasks = results.map((result) => ({
8036
+ name: result.name,
8037
+ latency: result.latency,
8038
+ throughput: result.throughput,
8039
+ period: result.period,
8040
+ totalTime: result.totalTime,
8041
+ rank: 0,
8042
+ ...taskMeta?.get(result.name)
8043
+ }));
7631
8044
  if (fromTasks) tasks.push(...fromTasks);
7632
8045
  tasks.sort((a, b) => a.latency.mean - b.latency.mean);
7633
8046
  tasks.forEach((task, idx) => {
7634
8047
  task.rank = idx + 1;
7635
8048
  });
7636
8049
  return {
7637
- name: name || test.fullTestName,
8050
+ name,
7638
8051
  tasks
7639
8052
  };
7640
8053
  };
7641
- const recordBenchmark = async (tinybenchTasks, name, taskMeta, fromTasks) => {
7642
- const serializedBenchmark = serializeBenchmark(tinybenchTasks, name, taskMeta, fromTasks);
8054
+ const recordBenchmark = async (results, name, taskMeta, fromTasks) => {
8055
+ const serializedBenchmark = serializeBenchmark(results, name, taskMeta, fromTasks);
7643
8056
  test.benchmarks.push(serializedBenchmark);
7644
8057
  await rpc().onTestBenchmark(test.id, serializedBenchmark);
7645
8058
  };
@@ -7653,19 +8066,28 @@ function createBench(test, config) {
7653
8066
  };
7654
8067
  await rpc().writeBenchmarkResult(resolved, data);
7655
8068
  };
7656
- const runBenchmarks = async (tinybench) => {
8069
+ const groupName = (options) => options?.name ?? test.fullTestName;
8070
+ const runGroup = async (registrations, options) => {
7657
8071
  const workerState = getWorkerState();
7658
8072
  const getterTracker = workerState.getterTracker;
7659
8073
  getterTracker?.resetInvocations();
7660
8074
  try {
7661
- return await TestRunner.runBenchmarks(tinybench);
8075
+ const results = await (await resolveBenchmarkProvider(config, moduleRunner)).run({
8076
+ test,
8077
+ config: config.benchmark,
8078
+ registrations,
8079
+ options
8080
+ });
8081
+ const byName = /* @__PURE__ */ new Map();
8082
+ for (const result of results) byName.set(result.name, result);
8083
+ return byName;
7662
8084
  } finally {
7663
8085
  const excessiveInvocations = config.benchmark.suppressExportGetterWarnings ? void 0 : getterTracker?.getExcessiveInvocations();
7664
8086
  if (excessiveInvocations?.length) {
7665
8087
  const entries = excessiveInvocations.map(({ moduleId, exportName }) => ` - ${formatModuleId(moduleId, workerState.config.root)} > ${exportName}`).join("\n");
7666
8088
  console.warn([
7667
- c.yellow(c.bold("Benchmark Warning")),
7668
- `Benchmark ${c.bold(`"${tinybench.name}"`)} accessed module export getters too many times.`,
8089
+ y.yellow(y.bold("Benchmark Warning")),
8090
+ `Benchmark ${y.bold(`"${groupName(options)}"`)} accessed module export getters too many times.`,
7669
8091
  "",
7670
8092
  "This can make results unreliable because export getters add overhead.",
7671
8093
  "See https://vitest.dev/guide/benchmarking#module-runner-overhead",
@@ -7677,13 +8099,15 @@ function createBench(test, config) {
7677
8099
  }
7678
8100
  };
7679
8101
  const runSingle = async (name, fn, fnOpts, options, meta, writeResult) => {
7680
- const tinybench = createTinybench(options).add(name, fn, fnOpts);
7681
- const tasks = await runBenchmarks(tinybench);
7682
- const task = tinybench.getTask(name);
7683
- if (task.result.state === "errored") throw task.result.error;
7684
- await recordBenchmark(tasks, tinybench.name, meta ? new Map([[name, meta]]) : void 0);
7685
- if (writeResult) await writeResultArtifact(writeResult, task.result);
7686
- return task.result;
8102
+ const result = (await runGroup([{
8103
+ name,
8104
+ fn,
8105
+ fnOpts
8106
+ }], options)).get(name);
8107
+ if (!result) throw new Error(`benchmark provider did not return a result for "${name}"`);
8108
+ await recordBenchmark([result], groupName(options), meta ? new Map([[name, meta]]) : void 0);
8109
+ if (writeResult) await writeResultArtifact(writeResult, result);
8110
+ return result;
7687
8111
  };
7688
8112
  const runFrom = async (name, source) => {
7689
8113
  const data = await resolveFromSource(source);
@@ -7737,8 +8161,8 @@ function createBench(test, config) {
7737
8161
  };
7738
8162
  bench.compare = async (...args) => {
7739
8163
  validateBenchmarkProject(config);
7740
- // extract optional trailing BenchCompareOptions argument
7741
- const lastArg = args[args.length - 1];
8164
+ // extract optional trailing BenchRunOptions argument
8165
+ const lastArg = args.at(-1);
7742
8166
  const benchOptions = lastArg != null && typeof lastArg === "object" && !(kRegistration in lastArg) ? args.pop() : void 0;
7743
8167
  const registrations = args;
7744
8168
  // Mark every passed-in registration as consumed before validation so a
@@ -7767,33 +8191,29 @@ function createBench(test, config) {
7767
8191
  fromTasks.push(taskFromBaseline(reg.name, data));
7768
8192
  }
7769
8193
  }
7770
- const tinybench = createTinybench(benchOptions);
7771
- runnable.forEach((reg) => {
7772
- tinybench.add(reg.name, reg.fn, reg.fnOpts);
7773
- });
7774
- let tasks = [];
7775
- if (runnable.length > 0) {
7776
- tasks = await runBenchmarks(tinybench);
7777
- const errors = tinybench.tasks.filter((task) => task.result.state === "errored").map((task) => task.result.error);
7778
- if (errors.length > 0) throw new AggregateError(errors, "Some benchmarks failed");
7779
- }
7780
- await recordBenchmark(tasks, tinybench.name, taskMeta, fromTasks);
8194
+ let results = /* @__PURE__ */ new Map();
8195
+ if (runnable.length > 0) results = await runGroup(runnable.map((reg) => ({
8196
+ name: reg.name,
8197
+ fn: reg.fn,
8198
+ fnOpts: reg.fnOpts
8199
+ })), benchOptions);
8200
+ await recordBenchmark(Array.from(results.values()), groupName(benchOptions), taskMeta, fromTasks);
7781
8201
  // write artifacts for every runnable registration that requested it. We
7782
8202
  // do this after recording so a write failure can't be confused with a
7783
8203
  // benchmark failure in the reporter output.
7784
8204
  await Promise.all(runnable.filter((reg) => reg[kWriteResult] != null).map((reg) => {
7785
- const task = tinybench.getTask(reg.name);
7786
- return writeResultArtifact(reg[kWriteResult], task.result);
8205
+ const result = results.get(reg.name);
8206
+ return writeResultArtifact(reg[kWriteResult], result);
7787
8207
  }));
7788
- return createCompareStorage(tinybench, fromResults);
8208
+ return createCompareStorage(results, fromResults);
7789
8209
  };
7790
8210
  bench[kFinalize] = () => {
7791
8211
  if (pending.size === 0) return;
7792
- const names = [...pending].map((reg) => `"${reg.name}"`).join(", ");
8212
+ const names = Array.from(pending, (reg) => `"${reg.name}"`).join(", ");
7793
8213
  pending.clear();
7794
8214
  console.warn([
7795
- c.yellow(c.bold("Benchmark Warning")),
7796
- `Test ${c.bold(`"${test.fullTestName}"`)} registered benchmarks that never ran: ${names}.`,
8215
+ y.yellow(y.bold("Benchmark Warning")),
8216
+ `Test ${y.bold(`"${test.fullTestName}"`)} registered benchmarks that never ran: ${names}.`,
7797
8217
  "",
7798
8218
  "Call `.run()` on the registration, or pass it to `bench.compare()`.",
7799
8219
  "See https://vitest.dev/guide/benchmarking#defining-a-benchmark"
@@ -7818,8 +8238,8 @@ function normalizeBenchArgs(a, b) {
7818
8238
  if (typeof b !== "function") throw new TypeError("`bench()` expects a benchmark function. Call `bench(name, fn)` or `bench(name, options, fn)`.");
7819
8239
  // Strip vitest-specific fields only when present so we don't allocate a new
7820
8240
  // object — preserving referential identity matters: users inspect
7821
- // `registration.fnOpts` and tinybench's `add` sees the same object the
7822
- // caller passed in.
8241
+ // `registration.fnOpts` and the provider sees the same object the caller
8242
+ // passed in.
7823
8243
  if (a.writeResult === void 0 && a.perProject === void 0) return {
7824
8244
  fn: b,
7825
8245
  fnOpts: a,
@@ -7955,9 +8375,10 @@ class TestRunner {
7955
8375
  let _bench;
7956
8376
  const runnerConfig = this.config;
7957
8377
  const benchInstances = this.benchInstances;
8378
+ const moduleRunner = this.moduleRunner;
7958
8379
  Object.defineProperty(context, "bench", { get() {
7959
8380
  if (!_bench) {
7960
- _bench = createBench(context.task, runnerConfig);
8381
+ _bench = createBench(context.task, runnerConfig, moduleRunner);
7961
8382
  benchInstances.set(context.task, _bench);
7962
8383
  }
7963
8384
  return _bench;
@@ -7996,14 +8417,6 @@ class TestRunner {
7996
8417
  static setTestFn = getFn;
7997
8418
  static matchesTags = matchesTags;
7998
8419
  static createFileTask = createFileTask;
7999
- /**
8000
- * @experimental
8001
- * A function that runs tinybench tasks.
8002
- * Can be overriden to run tasks in a special environment.
8003
- */
8004
- static async runBenchmarks(tinybench) {
8005
- return await tinybench.run();
8006
- }
8007
8420
  }
8008
8421
  function clearModuleMocks(config) {
8009
8422
  const { clearMocks, mockReset, restoreMocks, unstubEnvs, unstubGlobals } = config;