vitest 0.0.92 → 0.0.96

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/bin/vitest.mjs CHANGED
@@ -6,6 +6,9 @@ import { ensurePackageInstalled } from '../dist/utils.js'
6
6
 
7
7
  const argv = process.argv.slice(2)
8
8
 
9
+ if (!await ensurePackageInstalled('vite'))
10
+ process.exit(1)
11
+
9
12
  if (argv.includes('--coverage')) {
10
13
  if (!await ensurePackageInstalled('c8'))
11
14
  process.exit(1)
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import require$$0 from 'readline';
2
2
  import { EventEmitter } from 'events';
3
- import { c, e as ensurePackageInstalled } from './utils-860e5f7e.js';
4
- import { c as createVitest } from './index-40ecbcb4.js';
3
+ import { c, e as ensurePackageInstalled } from './utils-576876dc.js';
4
+ import { c as createVitest } from './index-ece64e3c.js';
5
5
  import 'tty';
6
6
  import 'local-pkg';
7
7
  import 'path';
@@ -13,12 +13,12 @@ import 'util';
13
13
  import './constants-9cfa4d7b.js';
14
14
  import 'url';
15
15
  import 'perf_hooks';
16
- import './error-4b0c4b4b.js';
16
+ import './error-81292c96.js';
17
17
  import 'source-map';
18
18
  import './index-5cc247ff.js';
19
19
  import 'assert';
20
20
  import 'worker_threads';
21
- import 'piscina';
21
+ import 'tinypool';
22
22
 
23
23
  function toArr(any) {
24
24
  return any == null ? [] : Array.isArray(any) ? any : [any];
@@ -633,7 +633,7 @@ class CAC extends EventEmitter {
633
633
 
634
634
  const cac = (name = "") => new CAC(name);
635
635
 
636
- var version = "0.0.92";
636
+ var version = "0.0.96";
637
637
 
638
638
  const cli = cac("vitest");
639
639
  cli.version(version).option("-r, --root <path>", "root path").option("-c, --config <path>", "path to config file").option("-u, --update", "update snapshot").option("-w, --watch", "watch mode").option("-o, --open", "open Vitest UI").option("--api", "listen to port and serve API").option("--threads", "enabled threads", { default: true }).option("--silent", "silent").option("--global", "inject apis globally").option("--dom", "mock browser api with happy-dom").option("--environment <env>", "runner environment", {
@@ -654,16 +654,18 @@ async function run(cliFilters, options) {
654
654
  process.env.NODE_ENV = "test";
655
655
  if (!options.silent) {
656
656
  console.log(c.magenta(c.bold("\nVitest is in closed beta exclusively for Sponsors")));
657
- console.log(c.yellow("Learn more at https://vitest.dev\n"));
657
+ console.log(c.yellow("Learn more at https://vitest.dev"));
658
658
  }
659
659
  const ctx = await createVitest(options);
660
- process.__vitest__ = ctx;
661
660
  process.chdir(ctx.config.root);
662
661
  registerConsoleShortcuts(ctx);
663
662
  if (ctx.config.environment && ctx.config.environment !== "node") {
664
663
  if (!await ensurePackageInstalled(ctx.config.environment))
665
664
  process.exit(1);
666
665
  }
666
+ ctx.onServerRestarted(() => {
667
+ ctx.start(cliFilters);
668
+ });
667
669
  try {
668
670
  await ctx.start(cliFilters);
669
671
  } catch (e) {
package/dist/entry.js CHANGED
@@ -6,11 +6,12 @@ import chai, { expect, util } from 'chai';
6
6
  import Subset from 'chai-subset';
7
7
  import path, { basename } from 'path';
8
8
  import { r as rpc, s as send } from './rpc-7de86f29.js';
9
- import { g as getNames, c as c$1, t as toArray, i as interpretOnlyMode, p as partitionSuiteChildren, d as hasTests, h as hasFailed } from './utils-860e5f7e.js';
10
- import { u as unifiedDiff } from './error-4b0c4b4b.js';
9
+ import { g as getNames, c as c$1, t as toArray, i as interpretOnlyMode, p as partitionSuiteChildren, f as hasTests, h as hasFailed } from './utils-576876dc.js';
10
+ import { u as unifiedDiff } from './error-81292c96.js';
11
11
  import { performance } from 'perf_hooks';
12
12
  import { b as setHooks, c as createSuiteHooks, e as clearContext, f as defaultSuite, h as context, j as getHooks, k as getFn } from './suite-b8c6cb53.js';
13
13
  import { n as nanoid } from './index-9e71c815.js';
14
+ import { format as format$1 } from 'util';
14
15
  import 'tty';
15
16
  import 'source-map';
16
17
 
@@ -3413,7 +3414,7 @@ const JestChaiExpect = (chai, utils) => {
3413
3414
  const expected = args[0];
3414
3415
  const actual = utils.flag(this, "object");
3415
3416
  if (hasAsymmetric(expected)) {
3416
- this.assert(equals(actual, expected, void 0, true), "not match with #{this}", "should not match with #{this}", true);
3417
+ this.assert(equals(actual, expected, void 0, true), "not match with #{act}", "should not match with #{act}", actual, expected);
3417
3418
  } else {
3418
3419
  _super.apply(this, args);
3419
3420
  }
@@ -3424,7 +3425,7 @@ const JestChaiExpect = (chai, utils) => {
3424
3425
  const expected = args[0];
3425
3426
  const actual = utils.flag(this, "object");
3426
3427
  if (hasAsymmetric(expected)) {
3427
- this.assert(equals(actual, expected), "not match with #{this}", "should not match with #{this}", true);
3428
+ this.assert(equals(actual, expected), "not match with #{exp}", "should not match with #{exp}", actual, expected);
3428
3429
  } else {
3429
3430
  _super.apply(this, args);
3430
3431
  }
@@ -3437,7 +3438,7 @@ const JestChaiExpect = (chai, utils) => {
3437
3438
  return this.chaiEqual(expected);
3438
3439
  });
3439
3440
  def("toBe", function(expected) {
3440
- return this.chaiEqual(expected);
3441
+ return this.equal(expected);
3441
3442
  });
3442
3443
  def("toMatchObject", function(expected) {
3443
3444
  return this.containSubset(expected);
@@ -3534,7 +3535,7 @@ const JestChaiExpect = (chai, utils) => {
3534
3535
  def(["toHaveBeenCalledWith", "toBeCalledWith"], function(...args) {
3535
3536
  const spy = getSpy(this);
3536
3537
  const pass = spy.calls.some((callArg) => equals(callArg, args));
3537
- return this.assert(pass, "expected spy to be called with arguments: #{exp}", "expected spy to not be called with arguments: #{exp}", args);
3538
+ return this.assert(pass, "expected spy to be called with arguments: #{exp}", "expected spy to not be called with arguments: #{exp}", args, spy.calls);
3538
3539
  });
3539
3540
  const ordinalOf = (i) => {
3540
3541
  const j = i % 10;
@@ -3629,7 +3630,7 @@ class StringContaining extends AsymmetricMatcher {
3629
3630
  }
3630
3631
  class Anything extends AsymmetricMatcher {
3631
3632
  asymmetricMatch(other) {
3632
- return other !== void 0 && other !== null;
3633
+ return other != null;
3633
3634
  }
3634
3635
  toString() {
3635
3636
  return "Anything";
@@ -3638,11 +3639,127 @@ class Anything extends AsymmetricMatcher {
3638
3639
  return "Anything";
3639
3640
  }
3640
3641
  }
3642
+ class ObjectContaining extends AsymmetricMatcher {
3643
+ constructor(sample, inverse = false) {
3644
+ super(sample, inverse);
3645
+ }
3646
+ getPrototype(obj) {
3647
+ if (Object.getPrototypeOf)
3648
+ return Object.getPrototypeOf(obj);
3649
+ if (obj.constructor.prototype === obj)
3650
+ return null;
3651
+ return obj.constructor.prototype;
3652
+ }
3653
+ hasProperty(obj, property) {
3654
+ if (!obj)
3655
+ return false;
3656
+ if (Object.prototype.hasOwnProperty.call(obj, property))
3657
+ return true;
3658
+ return this.hasProperty(this.getPrototype(obj), property);
3659
+ }
3660
+ asymmetricMatch(other) {
3661
+ if (typeof this.sample !== "object") {
3662
+ throw new TypeError(`You must provide an object to ${this.toString()}, not '${typeof this.sample}'.`);
3663
+ }
3664
+ let result = true;
3665
+ for (const property in this.sample) {
3666
+ if (!this.hasProperty(other, property) || !equals(this.sample[property], other[property])) {
3667
+ result = false;
3668
+ break;
3669
+ }
3670
+ }
3671
+ return this.inverse ? !result : result;
3672
+ }
3673
+ toString() {
3674
+ return `Object${this.inverse ? "Not" : ""}Containing`;
3675
+ }
3676
+ getExpectedType() {
3677
+ return "object";
3678
+ }
3679
+ }
3680
+ class ArrayContaining extends AsymmetricMatcher {
3681
+ constructor(sample, inverse = false) {
3682
+ super(sample, inverse);
3683
+ }
3684
+ asymmetricMatch(other) {
3685
+ if (!Array.isArray(this.sample)) {
3686
+ throw new TypeError(`You must provide an array to ${this.toString()}, not '${typeof this.sample}'.`);
3687
+ }
3688
+ const result = this.sample.length === 0 || Array.isArray(other) && this.sample.every((item) => other.some((another) => equals(item, another)));
3689
+ return this.inverse ? !result : result;
3690
+ }
3691
+ toString() {
3692
+ return `Array${this.inverse ? "Not" : ""}Containing`;
3693
+ }
3694
+ getExpectedType() {
3695
+ return "array";
3696
+ }
3697
+ }
3698
+ class Any extends AsymmetricMatcher {
3699
+ constructor(sample) {
3700
+ if (typeof sample === "undefined") {
3701
+ throw new TypeError("any() expects to be passed a constructor function. Please pass one or use anything() to match any object.");
3702
+ }
3703
+ super(sample);
3704
+ }
3705
+ fnNameFor(func) {
3706
+ if (func.name)
3707
+ return func.name;
3708
+ const functionToString = Function.prototype.toString;
3709
+ const matches = functionToString.call(func).match(/^(?:async)?\s*function\s*\*?\s*([\w$]+)\s*\(/);
3710
+ return matches ? matches[1] : "<anonymous>";
3711
+ }
3712
+ asymmetricMatch(other) {
3713
+ if (this.sample === String)
3714
+ return typeof other == "string" || other instanceof String;
3715
+ if (this.sample === Number)
3716
+ return typeof other == "number" || other instanceof Number;
3717
+ if (this.sample === Function)
3718
+ return typeof other == "function" || other instanceof Function;
3719
+ if (this.sample === Boolean)
3720
+ return typeof other == "boolean" || other instanceof Boolean;
3721
+ if (this.sample === BigInt)
3722
+ return typeof other == "bigint" || other instanceof BigInt;
3723
+ if (this.sample === Symbol)
3724
+ return typeof other == "symbol" || other instanceof Symbol;
3725
+ if (this.sample === Object)
3726
+ return typeof other == "object";
3727
+ return other instanceof this.sample;
3728
+ }
3729
+ toString() {
3730
+ return "Any";
3731
+ }
3732
+ getExpectedType() {
3733
+ if (this.sample === String)
3734
+ return "string";
3735
+ if (this.sample === Number)
3736
+ return "number";
3737
+ if (this.sample === Function)
3738
+ return "function";
3739
+ if (this.sample === Object)
3740
+ return "object";
3741
+ if (this.sample === Boolean)
3742
+ return "boolean";
3743
+ return this.fnNameFor(this.sample);
3744
+ }
3745
+ toAsymmetricMatcher() {
3746
+ return `Any<${this.fnNameFor(this.sample)}>`;
3747
+ }
3748
+ }
3641
3749
  const JestAsymmetricMatchers = (chai, utils) => {
3642
3750
  utils.addMethod(chai.expect, "stringContaining", (expected) => new StringContaining(expected));
3643
3751
  utils.addMethod(chai.expect, "anything", () => {
3644
3752
  return new Anything();
3645
3753
  });
3754
+ utils.addMethod(chai.expect, "objectContaining", (expected) => {
3755
+ return new ObjectContaining(expected);
3756
+ });
3757
+ utils.addMethod(chai.expect, "any", (expected) => {
3758
+ return new Any(expected);
3759
+ });
3760
+ utils.addMethod(chai.expect, "arrayContaining", (expected) => {
3761
+ return new ArrayContaining(expected);
3762
+ });
3646
3763
  };
3647
3764
 
3648
3765
  let installed = false;
@@ -3665,7 +3782,7 @@ async function setupGlobalEnv(config) {
3665
3782
  setupConsoleLogSpy();
3666
3783
  await setupChai();
3667
3784
  if (config.global)
3668
- (await import('./global-c40aeb86.js')).registerApiGlobally();
3785
+ (await import('./global-473089f7.js')).registerApiGlobally();
3669
3786
  }
3670
3787
  function setupConsoleLogSpy() {
3671
3788
  const stdout = new Writable({
@@ -3725,6 +3842,8 @@ function serializeError(val) {
3725
3842
  return "Promise";
3726
3843
  if (typeof Element !== "undefined" && val instanceof Element)
3727
3844
  return val.tagName;
3845
+ if (typeof val.asymmetricMatch === "function")
3846
+ return `${val.toString()} ${format$1(val.sample)}`;
3728
3847
  Object.keys(val).forEach((key) => {
3729
3848
  val[key] = serializeError(val[key]);
3730
3849
  });
@@ -1,6 +1,7 @@
1
1
  import { existsSync, promises } from 'fs';
2
2
  import { relative } from 'path';
3
- import { n as notNullish, c } from './utils-860e5f7e.js';
3
+ import { format } from 'util';
4
+ import { d as notNullish, c } from './utils-576876dc.js';
4
5
  import { SourceMapConsumer } from 'source-map';
5
6
 
6
7
  function Diff() {}
@@ -1338,7 +1339,7 @@ function generateCodeFrame(source, indent = 0, start = 0, end, range = 2) {
1338
1339
  return res.join("\n");
1339
1340
  }
1340
1341
  function stringify(obj) {
1341
- return String(obj);
1342
+ return format(obj);
1342
1343
  }
1343
1344
  const stackFnCallRE = /at (.*) \((.+):(\d+):(\d+)\)$/;
1344
1345
  const stackBarePathRE = /at ?(.*) (.+):(\d+):(\d+)$/;
@@ -1373,35 +1374,34 @@ function unifiedDiff(actual, expected) {
1373
1374
  if (expectedLinesCount >= diffLimit)
1374
1375
  return;
1375
1376
  expectedLinesCount++;
1377
+ line = line[0] + " " + line.slice(1);
1376
1378
  const isLastLine = expectedLinesCount === diffLimit;
1377
- return indent + c.green(`${formatLine(line)} ${isLastLine ? renderTruncateMessage(indent) : ""}`);
1379
+ return indent + c.red(`${formatLine(line)} ${isLastLine ? renderTruncateMessage(indent) : ""}`);
1378
1380
  }
1379
1381
  if (line[0] === "-") {
1380
1382
  if (actualLinesCount >= diffLimit)
1381
1383
  return;
1382
1384
  actualLinesCount++;
1385
+ line = line[0] + " " + line.slice(1);
1383
1386
  const isLastLine = actualLinesCount === diffLimit;
1384
- return indent + c.red(`${formatLine(line)} ${isLastLine ? renderTruncateMessage(indent) : ""}`);
1387
+ return indent + c.green(`${formatLine(line)} ${isLastLine ? renderTruncateMessage(indent) : ""}`);
1385
1388
  }
1386
1389
  if (line.match(/@@/))
1387
1390
  return "--";
1388
1391
  if (line.match(/\\ No newline/))
1389
1392
  return null;
1390
- return indent + line;
1393
+ return indent + " " + line;
1391
1394
  }
1392
1395
  const msg = createPatch("string", actual, expected);
1393
1396
  const lines = msg.split("\n").splice(5);
1394
1397
  return `
1395
- ${indent}${c.red("- actual")}
1396
- ${indent}${c.green("+ expected")}
1398
+ ${indent}${c.green("- expected")}
1399
+ ${indent}${c.red("+ actual")}
1397
1400
 
1398
1401
  ${lines.map(cleanUp).filter(notBlank).join("\n")}`;
1399
1402
  }
1400
1403
  function formatLine(line) {
1401
- const lineLimitLength = 50;
1402
- if (line.length > lineLimitLength)
1403
- return `${line.slice(0, lineLimitLength)} ${c.dim("[...truncated]")}`;
1404
- return line;
1404
+ return cliTruncate(line, (process.stdout.columns || 40) - 1);
1405
1405
  }
1406
1406
  function renderTruncateMessage(indent) {
1407
1407
  return `
@@ -1,5 +1,5 @@
1
1
  import { g as globalApis } from './constants-9cfa4d7b.js';
2
- import { i as index } from './index-708135df.js';
2
+ import { i as index } from './index-368448f4.js';
3
3
  import 'path';
4
4
  import 'url';
5
5
  import './suite-b8c6cb53.js';
@@ -1,7 +1,7 @@
1
1
  import { g as getCurrentSuite, w as withTimeout, a as getDefaultHookTimeout, s as suite, t as test, d as describe, i as it } from './suite-b8c6cb53.js';
2
2
  import chai, { util, assert, should, expect } from 'chai';
3
3
  import * as tinyspy from 'tinyspy';
4
- import { spy, spyOn as spyOn$1 } from 'tinyspy';
4
+ import { spies } from 'tinyspy';
5
5
 
6
6
  const beforeAll = (fn, timeout) => getCurrentSuite().on("beforeAll", withTimeout(fn, timeout ?? getDefaultHookTimeout()));
7
7
  const afterAll = (fn, timeout) => getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout()));
@@ -136,7 +136,8 @@ class FakeTimers {
136
136
  const spyFactory = (spyType, resultBuilder) => {
137
137
  return (cb, ms = 0) => {
138
138
  const id = ++this._spyid;
139
- const nestedMs = ms + (this._nestedTime[this._scopeId] ?? 0);
139
+ const nestedTo = Object.entries(this._nestedTime).filter(([key]) => Number(key) <= this._scopeId);
140
+ const nestedMs = nestedTo.reduce((total, [, ms2]) => total + ms2, ms);
140
141
  const call = { id, cb, ms, nestedMs, scopeId: this._scopeId };
141
142
  const task = { type: spyType, call, nested: this._isNested };
142
143
  this.pushTask(task);
@@ -306,6 +307,24 @@ class VitestUtils {
306
307
  getTimerCount() {
307
308
  return this._timers.getTimerCount();
308
309
  }
310
+ isMockFunction(fn2) {
311
+ return typeof fn2 === "function" && "__isSpy" in fn2 && fn2.__isSpy;
312
+ }
313
+ clearAllMocks() {
314
+ spies.forEach((spy) => {
315
+ spy.reset();
316
+ });
317
+ }
318
+ resetAllMocks() {
319
+ spies.forEach((spy) => {
320
+ spy.reset();
321
+ });
322
+ }
323
+ restoreAllMocks() {
324
+ spies.forEach((spy) => {
325
+ spy.restore();
326
+ });
327
+ }
309
328
  }
310
329
  const vitest = new VitestUtils();
311
330
  const vi = vitest;
@@ -324,10 +343,10 @@ var index = /*#__PURE__*/Object.freeze({
324
343
  should: should,
325
344
  expect: expect,
326
345
  chai: chai,
327
- spy: spy,
328
- spyOn: spyOn$1,
346
+ spyOn: spyOn,
347
+ fn: fn,
329
348
  vitest: vitest,
330
349
  vi: vi
331
350
  });
332
351
 
333
- export { afterAll as a, beforeAll as b, beforeEach as c, afterEach as d, vi as e, index as i, vitest as v };
352
+ export { afterAll as a, beforeAll as b, beforeEach as c, afterEach as d, vi as e, fn as f, index as i, spyOn as s, vitest as v };
@@ -5,13 +5,13 @@ import { promises } from 'fs';
5
5
  import fg from 'fast-glob';
6
6
  import require$$0 from 'util';
7
7
  import { d as defaultInclude, a as defaultExclude, b as defaultPort, c as distDir, e as configFiles } from './constants-9cfa4d7b.js';
8
- import { c, g as getNames, s as slash, a as getTests, b as getSuites, t as toArray, h as hasFailed } from './utils-860e5f7e.js';
8
+ import { c, g as getNames, s as slash, a as getTests, b as getSuites, n as noop, t as toArray, h as hasFailed } from './utils-576876dc.js';
9
9
  import { performance } from 'perf_hooks';
10
- import { s as stringWidth, a as ansiStyles, b as stripAnsi, c as sliceAnsi, F as F_POINTER, d as F_DOWN, e as F_LONG_DASH, f as F_DOWN_RIGHT, g as F_DOT, h as F_CHECK, i as F_CROSS, j as cliTruncate, k as F_RIGHT, p as printError } from './error-4b0c4b4b.js';
10
+ import { s as stringWidth, a as ansiStyles, b as stripAnsi, c as sliceAnsi, F as F_POINTER, d as F_DOWN, e as F_LONG_DASH, f as F_DOWN_RIGHT, g as F_DOT, h as F_CHECK, i as F_CROSS, j as cliTruncate, k as F_RIGHT, p as printError } from './error-81292c96.js';
11
11
  import { o as onetime, s as signalExit } from './index-5cc247ff.js';
12
12
  import { MessageChannel } from 'worker_threads';
13
13
  import { pathToFileURL } from 'url';
14
- import Piscina from 'piscina';
14
+ import { Tinypool } from 'tinypool';
15
15
 
16
16
  /*
17
17
  How it works:
@@ -4971,7 +4971,8 @@ class ConsoleReporter {
4971
4971
  this.console = globalThis.console;
4972
4972
  this.isFirstWatchRun = true;
4973
4973
  const mode = ctx.config.watch ? c.yellow(" DEV ") : c.cyan(" RUN ");
4974
- this.log(`${c.inverse(c.bold(mode))} ${c.gray(this.ctx.config.root)}
4974
+ this.log(`
4975
+ ${c.inverse(c.bold(mode))} ${c.gray(this.ctx.config.root)}
4975
4976
  `);
4976
4977
  this.start = performance.now();
4977
4978
  }
@@ -5110,7 +5111,6 @@ ${c.bold(c.inverse(c.green(" PASS ")))}${c.green(" Waiting for file changes...")
5110
5111
  `);
5111
5112
  }
5112
5113
  onServerRestart() {
5113
- this.console.clear();
5114
5114
  this.log(c.cyan("Restarted due to config changes..."));
5115
5115
  }
5116
5116
  }
@@ -5263,7 +5263,7 @@ function createWorkerPool(ctx) {
5263
5263
  options.maxThreads = ctx.config.maxThreads;
5264
5264
  if (ctx.config.minThreads != null)
5265
5265
  options.minThreads = ctx.config.minThreads;
5266
- const piscina = new Piscina(options);
5266
+ const pool = new Tinypool(options);
5267
5267
  const runWithFiles = (name) => {
5268
5268
  return async (files, invalidates) => {
5269
5269
  await Promise.all(files.map(async (file) => {
@@ -5274,7 +5274,7 @@ function createWorkerPool(ctx) {
5274
5274
  files: [file],
5275
5275
  invalidates
5276
5276
  };
5277
- await piscina.run(data, { transferList: [workerPort], name });
5277
+ await pool.run(data, { transferList: [workerPort], name });
5278
5278
  port.close();
5279
5279
  workerPort.close();
5280
5280
  }));
@@ -5283,7 +5283,7 @@ function createWorkerPool(ctx) {
5283
5283
  return {
5284
5284
  runTests: runWithFiles("run"),
5285
5285
  collectTests: runWithFiles("collect"),
5286
- close: () => piscina.destroy()
5286
+ close: () => pool.destroy()
5287
5287
  };
5288
5288
  }
5289
5289
  function createChannel(ctx) {
@@ -5343,11 +5343,17 @@ class Vitest {
5343
5343
  this.invalidates = /* @__PURE__ */ new Set();
5344
5344
  this.changedTests = /* @__PURE__ */ new Set();
5345
5345
  this.isFirstRun = true;
5346
+ this.restartsCount = 0;
5347
+ this._onRestartListeners = [];
5348
+ this.unregisterWatcher = noop;
5346
5349
  this.console = globalThis.console;
5347
5350
  }
5348
5351
  setServer(options, server) {
5349
- var _a;
5350
- (_a = this.pool) == null ? void 0 : _a.close();
5352
+ var _a, _b;
5353
+ (_a = this.unregisterWatcher) == null ? void 0 : _a.call(this);
5354
+ clearTimeout(this._rerunTimer);
5355
+ this.restartsCount += 1;
5356
+ (_b = this.pool) == null ? void 0 : _b.close();
5351
5357
  this.pool = void 0;
5352
5358
  const resolved = resolveConfig(options, server.config);
5353
5359
  this.server = server;
@@ -5360,6 +5366,7 @@ class Vitest {
5360
5366
  if (this.config.watch)
5361
5367
  this.registerWatcher();
5362
5368
  this.runningPromise = void 0;
5369
+ this._onRestartListeners.forEach((fn) => fn());
5363
5370
  }
5364
5371
  async start(filters) {
5365
5372
  const files = await this.globTestFiles(filters);
@@ -5368,11 +5375,8 @@ class Vitest {
5368
5375
  process.exitCode = 1;
5369
5376
  }
5370
5377
  await this.runFiles(files);
5371
- if (this.config.watch) {
5378
+ if (this.config.watch)
5372
5379
  await this.report("onWatcherStart");
5373
- await new Promise(() => {
5374
- });
5375
- }
5376
5380
  }
5377
5381
  async runFiles(files) {
5378
5382
  await this.runningPromise;
@@ -5390,44 +5394,59 @@ class Vitest {
5390
5394
  });
5391
5395
  return await this.runningPromise;
5392
5396
  }
5397
+ async scheduleRerun(triggerId) {
5398
+ const currentCount = this.restartsCount;
5399
+ clearTimeout(this._rerunTimer);
5400
+ await this.runningPromise;
5401
+ clearTimeout(this._rerunTimer);
5402
+ if (this.restartsCount !== currentCount)
5403
+ return;
5404
+ this._rerunTimer = setTimeout(async () => {
5405
+ if (this.changedTests.size === 0) {
5406
+ this.invalidates.clear();
5407
+ return;
5408
+ }
5409
+ if (this.restartsCount !== currentCount)
5410
+ return;
5411
+ this.isFirstRun = false;
5412
+ const files = Array.from(this.changedTests);
5413
+ await this.report("onWatcherRerun", files, triggerId);
5414
+ await this.runFiles(files);
5415
+ await this.report("onWatcherStart");
5416
+ }, WATCHER_DEBOUNCE);
5417
+ }
5393
5418
  registerWatcher() {
5394
- let timer;
5395
- const scheduleRerun = async (id) => {
5396
- await this.runningPromise;
5397
- clearTimeout(timer);
5398
- timer = setTimeout(async () => {
5399
- if (this.changedTests.size === 0) {
5400
- this.invalidates.clear();
5401
- return;
5402
- }
5403
- this.isFirstRun = false;
5404
- const files = Array.from(this.changedTests);
5405
- await this.report("onWatcherRerun", files, id);
5406
- await this.runFiles(files);
5407
- await this.report("onWatcherStart");
5408
- }, WATCHER_DEBOUNCE);
5409
- };
5410
- this.server.watcher.on("change", (id) => {
5419
+ const onChange = (id) => {
5411
5420
  id = slash(id);
5412
5421
  this.handleFileChanged(id);
5413
5422
  if (this.changedTests.size)
5414
- scheduleRerun(id);
5415
- });
5416
- this.server.watcher.on("unlink", (id) => {
5423
+ this.scheduleRerun(id);
5424
+ };
5425
+ const onUnlink = (id) => {
5417
5426
  id = slash(id);
5418
5427
  this.invalidates.add(id);
5419
5428
  if (id in this.state.filesMap) {
5420
5429
  delete this.state.filesMap[id];
5421
5430
  this.changedTests.delete(id);
5422
5431
  }
5423
- });
5424
- this.server.watcher.on("add", async (id) => {
5432
+ };
5433
+ const onAdd = (id) => {
5425
5434
  id = slash(id);
5426
5435
  if (this.isTargetFile(id)) {
5427
5436
  this.changedTests.add(id);
5428
- scheduleRerun(id);
5437
+ this.scheduleRerun(id);
5429
5438
  }
5430
- });
5439
+ };
5440
+ const watcher = this.server.watcher;
5441
+ watcher.on("change", onChange);
5442
+ watcher.on("unlink", onUnlink);
5443
+ watcher.on("add", onAdd);
5444
+ this.unregisterWatcher = () => {
5445
+ watcher.off("change", onChange);
5446
+ watcher.off("unlink", onUnlink);
5447
+ watcher.off("add", onAdd);
5448
+ this.unregisterWatcher = noop;
5449
+ };
5431
5450
  }
5432
5451
  handleFileChanged(id) {
5433
5452
  if (this.changedTests.has(id) || this.invalidates.has(id) || id.includes("/node_modules/") || id.includes("/vitest/dist/"))
@@ -5471,6 +5490,9 @@ class Vitest {
5471
5490
  return false;
5472
5491
  return micromatch_1.isMatch(id, this.config.include);
5473
5492
  }
5493
+ onServerRestarted(fn) {
5494
+ this._onRestartListeners.push(fn);
5495
+ }
5474
5496
  }
5475
5497
  async function createVitest(options, viteOverrides = {}) {
5476
5498
  const ctx = new Vitest();
@@ -5491,7 +5513,7 @@ async function createVitest(options, viteOverrides = {}) {
5491
5513
  ctx.setServer(options, server2);
5492
5514
  haveStarted = true;
5493
5515
  if (options.api)
5494
- server2.middlewares.use((await import('./middleware-b1884a99.js')).default());
5516
+ server2.middlewares.use((await import('./middleware-bf0f818d.js')).default(ctx));
5495
5517
  }
5496
5518
  }
5497
5519
  ],
@@ -5507,6 +5529,8 @@ async function createVitest(options, viteOverrides = {}) {
5507
5529
  };
5508
5530
  const server = await createServer(mergeConfig(config, viteOverrides));
5509
5531
  await server.pluginContainer.buildStart({});
5532
+ if (options.api === true)
5533
+ options.api = defaultPort;
5510
5534
  if (typeof options.api === "number")
5511
5535
  await server.listen(options.api);
5512
5536
  return ctx;