uql-orm 0.3.0 → 0.3.2

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.
@@ -1477,7 +1477,25 @@ function nodeIsComment(node) {
1477
1477
  function nodeIsFragment(node) {
1478
1478
  return node.nodeType === FRAGMENT_NODE;
1479
1479
  }
1480
- const serialize$3 = (node, config, indentation, depth, refs, printer)=>{
1480
+ function filterChildren(children, filterNode) {
1481
+ // Filter out text nodes that only contain whitespace to prevent empty lines
1482
+ // This is done regardless of whether a filterNode is provided
1483
+ let filtered = children.filter((node)=>{
1484
+ // Filter out text nodes that are only whitespace
1485
+ if (node.nodeType === TEXT_NODE) {
1486
+ const text = node.data || "";
1487
+ // Keep text nodes that have non-whitespace content
1488
+ return text.trim().length > 0;
1489
+ }
1490
+ return true;
1491
+ });
1492
+ // Apply additional user-provided filter if specified
1493
+ if (filterNode) {
1494
+ filtered = filtered.filter(filterNode);
1495
+ }
1496
+ return filtered;
1497
+ }
1498
+ function serializeDOM(node, config, indentation, depth, refs, printer, filterNode) {
1481
1499
  if (nodeIsText(node)) {
1482
1500
  return printText(node.data, config);
1483
1501
  }
@@ -1488,13 +1506,24 @@ const serialize$3 = (node, config, indentation, depth, refs, printer)=>{
1488
1506
  if (++depth > config.maxDepth) {
1489
1507
  return printElementAsLeaf(type, config);
1490
1508
  }
1509
+ const children = Array.prototype.slice.call(node.childNodes || node.children);
1510
+ const shadowChildren = nodeIsFragment(node) || !node.shadowRoot ? [] : Array.prototype.slice.call(node.shadowRoot.children);
1511
+ const resolvedChildren = filterNode ? filterChildren(children, filterNode) : children;
1512
+ const resolvedShadowChildren = filterNode ? filterChildren(shadowChildren, filterNode) : shadowChildren;
1491
1513
  return printElement(type, printProps(nodeIsFragment(node) ? [] : Array.from(node.attributes, (attr)=>attr.name).sort(), nodeIsFragment(node) ? {} : [
1492
1514
  ...node.attributes
1493
1515
  ].reduce((props, attribute)=>{
1494
1516
  props[attribute.name] = attribute.value;
1495
1517
  return props;
1496
- }, {}), config, indentation + config.indent, depth, refs, printer), (nodeIsFragment(node) || !node.shadowRoot ? "" : printShadowRoot(Array.prototype.slice.call(node.shadowRoot.children), config, indentation + config.indent, depth, refs, printer)) + printChildren(Array.prototype.slice.call(node.childNodes || node.children), config, indentation + config.indent, depth, refs, printer), config, indentation);
1497
- };
1518
+ }, {}), config, indentation + config.indent, depth, refs, printer), (resolvedShadowChildren.length > 0 ? printShadowRoot(resolvedShadowChildren, config, indentation + config.indent, depth, refs, printer) : "") + printChildren(resolvedChildren, config, indentation + config.indent, depth, refs, printer), config, indentation);
1519
+ }
1520
+ const serialize$3 = (node, config, indentation, depth, refs, printer)=>serializeDOM(node, config, indentation, depth, refs, printer);
1521
+ function createDOMElementFilter(filterNode) {
1522
+ return {
1523
+ test: test$3,
1524
+ serialize: (node, config, indentation, depth, refs, printer)=>serializeDOM(node, config, indentation, depth, refs, printer, filterNode)
1525
+ };
1526
+ }
1498
1527
  const plugin$3 = {
1499
1528
  serialize: serialize$3,
1500
1529
  test: test$3
@@ -2861,14 +2890,24 @@ const PLUGINS = [
2861
2890
  Immutable,
2862
2891
  AsymmetricMatcher
2863
2892
  ];
2864
- function stringify(object, maxDepth = 10, { maxLength, ...options } = {}) {
2893
+ function stringify(object, maxDepth = 10, { maxLength, filterNode, ...options } = {}) {
2865
2894
  const MAX_LENGTH = maxLength ?? 1e4;
2866
2895
  let result;
2896
+ // Convert string selector to filter function
2897
+ const filterFn = typeof filterNode === "string" ? createNodeFilterFromSelector(filterNode) : filterNode;
2898
+ const plugins = filterFn ? [
2899
+ ReactTestComponent,
2900
+ ReactElement,
2901
+ createDOMElementFilter(filterFn),
2902
+ DOMCollection,
2903
+ Immutable,
2904
+ AsymmetricMatcher
2905
+ ] : PLUGINS;
2867
2906
  try {
2868
2907
  result = format$1(object, {
2869
2908
  maxDepth,
2870
2909
  escapeString: false,
2871
- plugins: PLUGINS,
2910
+ plugins,
2872
2911
  ...options
2873
2912
  });
2874
2913
  } catch {
@@ -2876,16 +2915,36 @@ function stringify(object, maxDepth = 10, { maxLength, ...options } = {}) {
2876
2915
  callToJSON: false,
2877
2916
  maxDepth,
2878
2917
  escapeString: false,
2879
- plugins: PLUGINS,
2918
+ plugins,
2880
2919
  ...options
2881
2920
  });
2882
2921
  }
2883
2922
  // Prevents infinite loop https://github.com/vitest-dev/vitest/issues/7249
2884
2923
  return result.length >= MAX_LENGTH && maxDepth > 1 ? stringify(object, Math.floor(Math.min(maxDepth, Number.MAX_SAFE_INTEGER) / 2), {
2885
2924
  maxLength,
2925
+ filterNode,
2886
2926
  ...options
2887
2927
  }) : result;
2888
2928
  }
2929
+ function createNodeFilterFromSelector(selector) {
2930
+ const ELEMENT_NODE = 1;
2931
+ const COMMENT_NODE = 8;
2932
+ return (node)=>{
2933
+ // Filter out comments
2934
+ if (node.nodeType === COMMENT_NODE) {
2935
+ return false;
2936
+ }
2937
+ // Filter out elements matching the selector
2938
+ if (node.nodeType === ELEMENT_NODE && node.matches) {
2939
+ try {
2940
+ return !node.matches(selector);
2941
+ } catch {
2942
+ return true;
2943
+ }
2944
+ }
2945
+ return true;
2946
+ };
2947
+ }
2889
2948
  const formatRegExp = /%[sdjifoOc%]/g;
2890
2949
  function baseFormat(args, options = {}) {
2891
2950
  const formatArg = (item, inspecOptions)=>{
@@ -2944,6 +3003,9 @@ function baseFormat(args, options = {}) {
2944
3003
  if (typeof value === "bigint") {
2945
3004
  return `${value.toString()}n`;
2946
3005
  }
3006
+ if (typeof value === "symbol") {
3007
+ return "NaN";
3008
+ }
2947
3009
  return Number(value).toString();
2948
3010
  }
2949
3011
  case "%i":
@@ -2984,7 +3046,7 @@ function baseFormat(args, options = {}) {
2984
3046
  });
2985
3047
  for(let x = args[i]; i < len; x = args[++i]){
2986
3048
  if (x === null || typeof x !== "object") {
2987
- str += ` ${x}`;
3049
+ str += ` ${typeof x === "symbol" ? x.toString() : x}`;
2988
3050
  } else {
2989
3051
  str += ` ${formatArg(x)}`;
2990
3052
  }
@@ -3030,6 +3092,31 @@ function assertTypes(value, name, types) {
3030
3092
  throw new TypeError(`${name} value must be ${types.join(" or ")}, received "${receivedType}"`);
3031
3093
  }
3032
3094
  }
3095
+ function filterOutComments(s) {
3096
+ const result = [];
3097
+ let commentState = "none";
3098
+ for(let i = 0; i < s.length; ++i){
3099
+ if (commentState === "singleline") {
3100
+ if (s[i] === "\n") {
3101
+ commentState = "none";
3102
+ }
3103
+ } else if (commentState === "multiline") {
3104
+ if (s[i - 1] === "*" && s[i] === "/") {
3105
+ commentState = "none";
3106
+ }
3107
+ } else if (commentState === "none") {
3108
+ if (s[i] === "/" && s[i + 1] === "/") {
3109
+ commentState = "singleline";
3110
+ } else if (s[i] === "/" && s[i + 1] === "*") {
3111
+ commentState = "multiline";
3112
+ i += 2;
3113
+ } else {
3114
+ result.push(s[i]);
3115
+ }
3116
+ }
3117
+ }
3118
+ return result.join("");
3119
+ }
3033
3120
  function toArray(array) {
3034
3121
  if (array === null || array === undefined) {
3035
3122
  array = [];
@@ -3077,6 +3164,23 @@ function isNegativeNaN(val) {
3077
3164
  const isNegative = u32[1] >>> 31 === 1;
3078
3165
  return isNegative;
3079
3166
  }
3167
+ function ordinal(i) {
3168
+ const j = i % 10;
3169
+ const k = i % 100;
3170
+ if (j === 1 && k !== 11) {
3171
+ return `${i}st`;
3172
+ }
3173
+ if (j === 2 && k !== 12) {
3174
+ return `${i}nd`;
3175
+ }
3176
+ if (j === 3 && k !== 13) {
3177
+ return `${i}rd`;
3178
+ }
3179
+ return `${i}th`;
3180
+ }
3181
+ function unique(array) {
3182
+ return Array.from(new Set(array));
3183
+ }
3080
3184
 
3081
3185
  const SAFE_TIMERS_SYMBOL = Symbol("vitest:SAFE_TIMERS");
3082
3186
  function getSafeTimers() {
@@ -3200,6 +3304,8 @@ for(let i = 0; i < chars.length; i++){
3200
3304
  }
3201
3305
  const CHROME_IE_STACK_REGEXP = /^\s*at .*(?:\S:\d+|\(native\))/m;
3202
3306
  const SAFARI_NATIVE_CODE_REGEXP = /^(?:eval@)?(?:\[native code\])?$/;
3307
+ const NOW_LENGTH = Date.now().toString().length;
3308
+ const REGEXP_VITEST = new RegExp(`vitest=\\d{${NOW_LENGTH}}`);
3203
3309
  function extractLocation(urlLike) {
3204
3310
  // Fail-fast but return locations like "(native)"
3205
3311
  if (!urlLike.includes(":")) {
@@ -3228,6 +3334,9 @@ function extractLocation(urlLike) {
3228
3334
  const isWindows = /^\/@fs\/[a-zA-Z]:\//.test(url);
3229
3335
  url = url.slice(isWindows ? 5 : 4);
3230
3336
  }
3337
+ if (url.includes("vitest=")) {
3338
+ url = url.replace(REGEXP_VITEST, "").replace(/[?&]$/, "");
3339
+ }
3231
3340
  return [
3232
3341
  url,
3233
3342
  parts[2] || undefined,
@@ -3324,7 +3433,7 @@ function parseSingleV8Stack(raw) {
3324
3433
  // normalize Windows path (\ -> /)
3325
3434
  file = file.startsWith("node:") || file.startsWith("internal:") ? file : resolve(file);
3326
3435
  if (method) {
3327
- method = method.replace(/__vite_ssr_import_\d+__\./g, "").replace(/(Object\.)?__vite_ssr_export_default__\s?/g, "");
3436
+ method = method.replace(/\(0\s?,\s?__vite_ssr_import_\d+__.(\w+)\)/g, "$1").replace(/__(vite_ssr_import|vi_import)_\d+__\./g, "").replace(/(Object\.)?__vite_ssr_export_default__\s?/g, "");
3328
3437
  }
3329
3438
  return {
3330
3439
  method,
@@ -3334,19 +3443,29 @@ function parseSingleV8Stack(raw) {
3334
3443
  };
3335
3444
  }
3336
3445
 
3337
- function createChainable(keys, fn) {
3446
+ const kChainableContext = Symbol("kChainableContext");
3447
+ function getChainableContext(chainable) {
3448
+ return chainable?.[kChainableContext];
3449
+ }
3450
+ function createChainable(keys, fn, context) {
3338
3451
  function create(context) {
3339
3452
  const chain = function(...args) {
3340
3453
  return fn.apply(context, args);
3341
3454
  };
3342
3455
  Object.assign(chain, fn);
3343
- chain.withContext = ()=>chain.bind(context);
3344
- chain.setContext = (key, value)=>{
3345
- context[key] = value;
3346
- };
3347
- chain.mergeContext = (ctx)=>{
3348
- Object.assign(context, ctx);
3349
- };
3456
+ Object.defineProperty(chain, kChainableContext, {
3457
+ value: {
3458
+ withContext: ()=>chain.bind(context),
3459
+ getFixtures: ()=>context.fixtures,
3460
+ setContext: (key, value)=>{
3461
+ context[key] = value;
3462
+ },
3463
+ mergeContext: (ctx)=>{
3464
+ Object.assign(context, ctx);
3465
+ }
3466
+ },
3467
+ enumerable: false
3468
+ });
3350
3469
  for (const key of keys){
3351
3470
  Object.defineProperty(chain, key, {
3352
3471
  get () {
@@ -3359,8 +3478,11 @@ function createChainable(keys, fn) {
3359
3478
  }
3360
3479
  return chain;
3361
3480
  }
3362
- const chain = create({});
3363
- chain.fn = fn;
3481
+ const chain = create(context ?? {});
3482
+ Object.defineProperty(chain, "fn", {
3483
+ value: fn,
3484
+ enumerable: false
3485
+ });
3364
3486
  return chain;
3365
3487
  }
3366
3488
  function findTestFileStackTrace(testFilePath, error) {
@@ -3373,6 +3495,23 @@ function findTestFileStackTrace(testFilePath, error) {
3373
3495
  }
3374
3496
  }
3375
3497
  }
3498
+ function validateTags(config, tags) {
3499
+ if (!config.strictTags) {
3500
+ return;
3501
+ }
3502
+ const availableTags = new Set(config.tags.map((tag)=>tag.name));
3503
+ for (const tag of tags){
3504
+ if (!availableTags.has(tag)) {
3505
+ throw createNoTagsError(config.tags, tag);
3506
+ }
3507
+ }
3508
+ }
3509
+ function createNoTagsError(availableTags, tag, prefix = "tag") {
3510
+ if (!availableTags.length) {
3511
+ throw new Error(`The Vitest config does't define any "tags", cannot apply "${tag}" ${prefix} for this test. See: https://vitest.dev/guide/test-tags`);
3512
+ }
3513
+ throw new Error(`The ${prefix} "${tag}" is not defined in the configuration. Available tags are:\n${availableTags.map((t)=>`- ${t.name}${t.description ? `: ${t.description}` : ""}`).join("\n")}`);
3514
+ }
3376
3515
  function createTaskName(names, separator = " > ") {
3377
3516
  return names.filter((name)=>name !== undefined).join(separator);
3378
3517
  }
@@ -3385,6 +3524,21 @@ class PendingError extends Error {
3385
3524
  this.taskId = task.id;
3386
3525
  }
3387
3526
  }
3527
+ class FixtureDependencyError extends Error {
3528
+ constructor(...args){
3529
+ super(...args), this.name = "FixtureDependencyError";
3530
+ }
3531
+ }
3532
+ class FixtureAccessError extends Error {
3533
+ constructor(...args){
3534
+ super(...args), this.name = "FixtureAccessError";
3535
+ }
3536
+ }
3537
+ class FixtureParseError extends Error {
3538
+ constructor(...args){
3539
+ super(...args), this.name = "FixtureParseError";
3540
+ }
3541
+ }
3388
3542
  // use WeakMap here to make the Test and Suite object serializable
3389
3543
  const fnMap = new WeakMap();
3390
3544
  const testFixtureMap = new WeakMap();
@@ -3395,7 +3549,7 @@ function setFn(key, fn) {
3395
3549
  function setTestFixture(key, fixture) {
3396
3550
  testFixtureMap.set(key, fixture);
3397
3551
  }
3398
- function getTestFixture(key) {
3552
+ function getTestFixtures(key) {
3399
3553
  return testFixtureMap.get(key);
3400
3554
  }
3401
3555
  function setHooks(key, hooks) {
@@ -3404,179 +3558,312 @@ function setHooks(key, hooks) {
3404
3558
  function getHooks(key) {
3405
3559
  return hooksMap.get(key);
3406
3560
  }
3407
- function mergeScopedFixtures(testFixtures, scopedFixtures) {
3408
- const scopedFixturesMap = scopedFixtures.reduce((map, fixture)=>{
3409
- map[fixture.prop] = fixture;
3410
- return map;
3411
- }, {});
3412
- const newFixtures = {};
3413
- testFixtures.forEach((fixture)=>{
3414
- const useFixture = scopedFixturesMap[fixture.prop] || {
3415
- ...fixture
3416
- };
3417
- newFixtures[useFixture.prop] = useFixture;
3418
- });
3419
- for(const fixtureKep in newFixtures){
3420
- var _fixture$deps;
3421
- const fixture = newFixtures[fixtureKep];
3422
- // if the fixture was define before the scope, then its dep
3423
- // will reference the original fixture instead of the scope
3424
- fixture.deps = (_fixture$deps = fixture.deps) === null || _fixture$deps === void 0 ? void 0 : _fixture$deps.map((dep)=>newFixtures[dep.prop]);
3425
- }
3426
- return Object.values(newFixtures);
3427
- }
3428
- function mergeContextFixtures(fixtures, context, runner) {
3429
- const fixtureOptionKeys = [
3430
- "auto",
3431
- "injected",
3432
- "scope"
3433
- ];
3434
- const fixtureArray = Object.entries(fixtures).map(([prop, value])=>{
3435
- const fixtureItem = {
3436
- value
3561
+ class TestFixtures {
3562
+ static{
3563
+ this._definitions = [];
3564
+ }
3565
+ static{
3566
+ this._builtinFixtures = [
3567
+ "task",
3568
+ "signal",
3569
+ "onTestFailed",
3570
+ "onTestFinished",
3571
+ "skip",
3572
+ "annotate"
3573
+ ];
3574
+ }
3575
+ static{
3576
+ this._fixtureOptionKeys = [
3577
+ "auto",
3578
+ "injected",
3579
+ "scope"
3580
+ ];
3581
+ }
3582
+ static{
3583
+ this._fixtureScopes = [
3584
+ "test",
3585
+ "file",
3586
+ "worker"
3587
+ ];
3588
+ }
3589
+ static{
3590
+ this._workerContextSuite = {
3591
+ type: "worker"
3437
3592
  };
3438
- if (Array.isArray(value) && value.length >= 2 && isObject(value[1]) && Object.keys(value[1]).some((key)=>fixtureOptionKeys.includes(key))) {
3439
- var _runner$injectValue;
3440
- // fixture with options
3441
- Object.assign(fixtureItem, value[1]);
3442
- const userValue = value[0];
3443
- fixtureItem.value = fixtureItem.injected ? ((_runner$injectValue = runner.injectValue) === null || _runner$injectValue === void 0 ? void 0 : _runner$injectValue.call(runner, prop)) ?? userValue : userValue;
3593
+ }
3594
+ static clearDefinitions() {
3595
+ TestFixtures._definitions.length = 0;
3596
+ }
3597
+ static getWorkerContexts() {
3598
+ return TestFixtures._definitions.map((f)=>f.getWorkerContext());
3599
+ }
3600
+ static getFileContexts(file) {
3601
+ return TestFixtures._definitions.map((f)=>f.getFileContext(file));
3602
+ }
3603
+ constructor(registrations){
3604
+ this._overrides = new WeakMap();
3605
+ this._registrations = registrations ?? new Map();
3606
+ this._suiteContexts = new WeakMap();
3607
+ TestFixtures._definitions.push(this);
3608
+ }
3609
+ extend(runner, userFixtures) {
3610
+ const { suite } = getCurrentSuite();
3611
+ const isTopLevel = !suite || suite.file === suite;
3612
+ const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel);
3613
+ return new TestFixtures(registrations);
3614
+ }
3615
+ get(suite) {
3616
+ let currentSuite = suite;
3617
+ while(currentSuite){
3618
+ const overrides = this._overrides.get(currentSuite);
3619
+ // return the closest override
3620
+ if (overrides) {
3621
+ return overrides;
3622
+ }
3623
+ if (currentSuite === currentSuite.file) {
3624
+ break;
3625
+ }
3626
+ currentSuite = currentSuite.suite || currentSuite.file;
3444
3627
  }
3445
- fixtureItem.scope = fixtureItem.scope || "test";
3446
- if (fixtureItem.scope === "worker" && !runner.getWorkerContext) {
3447
- fixtureItem.scope = "file";
3628
+ return this._registrations;
3629
+ }
3630
+ override(runner, userFixtures) {
3631
+ const { suite: currentSuite, file } = getCurrentSuite();
3632
+ const suite = currentSuite || file;
3633
+ const isTopLevel = !currentSuite || currentSuite.file === currentSuite;
3634
+ // Create a copy of the closest parent's registrations to avoid modifying them
3635
+ // For chained calls, this.get(suite) returns this suite's overrides; for first call, returns parent's
3636
+ const suiteRegistrations = new Map(this.get(suite));
3637
+ const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel, suiteRegistrations);
3638
+ // If defined in top-level, just override all registrations
3639
+ // We don't support overriding suite-level fixtures anyway (it will throw an error)
3640
+ if (isTopLevel) {
3641
+ this._registrations = registrations;
3642
+ } else {
3643
+ this._overrides.set(suite, registrations);
3448
3644
  }
3449
- fixtureItem.prop = prop;
3450
- fixtureItem.isFn = typeof fixtureItem.value === "function";
3451
- return fixtureItem;
3452
- });
3453
- if (Array.isArray(context.fixtures)) {
3454
- context.fixtures = context.fixtures.concat(fixtureArray);
3455
- } else {
3456
- context.fixtures = fixtureArray;
3457
- }
3458
- // Update dependencies of fixture functions
3459
- fixtureArray.forEach((fixture)=>{
3460
- if (fixture.isFn) {
3461
- const usedProps = getUsedProps(fixture.value);
3462
- if (usedProps.length) {
3463
- fixture.deps = context.fixtures.filter(({ prop })=>prop !== fixture.prop && usedProps.includes(prop));
3645
+ }
3646
+ getFileContext(file) {
3647
+ if (!this._suiteContexts.has(file)) {
3648
+ this._suiteContexts.set(file, Object.create(null));
3649
+ }
3650
+ return this._suiteContexts.get(file);
3651
+ }
3652
+ getWorkerContext() {
3653
+ if (!this._suiteContexts.has(TestFixtures._workerContextSuite)) {
3654
+ this._suiteContexts.set(TestFixtures._workerContextSuite, Object.create(null));
3655
+ }
3656
+ return this._suiteContexts.get(TestFixtures._workerContextSuite);
3657
+ }
3658
+ parseUserFixtures(runner, userFixtures, supportNonTest, registrations = new Map(this._registrations)) {
3659
+ const errors = [];
3660
+ Object.entries(userFixtures).forEach(([name, fn])=>{
3661
+ let options;
3662
+ let value;
3663
+ let _options;
3664
+ if (Array.isArray(fn) && fn.length >= 2 && isObject(fn[1]) && Object.keys(fn[1]).some((key)=>TestFixtures._fixtureOptionKeys.includes(key))) {
3665
+ _options = fn[1];
3666
+ options = {
3667
+ auto: _options.auto ?? false,
3668
+ scope: _options.scope ?? "test",
3669
+ injected: _options.injected ?? false
3670
+ };
3671
+ value = options.injected ? runner.injectValue?.(name) ?? fn[0] : fn[0];
3672
+ } else {
3673
+ value = fn;
3464
3674
  }
3465
- // test can access anything, so we ignore it
3466
- if (fixture.scope !== "test") {
3467
- var _fixture$deps2;
3468
- (_fixture$deps2 = fixture.deps) === null || _fixture$deps2 === void 0 ? void 0 : _fixture$deps2.forEach((dep)=>{
3469
- if (!dep.isFn) {
3470
- // non fn fixtures are always resolved and available to anyone
3471
- return;
3472
- }
3473
- // worker scope can only import from worker scope
3474
- if (fixture.scope === "worker" && dep.scope === "worker") {
3475
- return;
3476
- }
3477
- // file scope an import from file and worker scopes
3478
- if (fixture.scope === "file" && dep.scope !== "test") {
3479
- return;
3480
- }
3481
- throw new SyntaxError(`cannot use the ${dep.scope} fixture "${dep.prop}" inside the ${fixture.scope} fixture "${fixture.prop}"`);
3482
- });
3675
+ const parent = registrations.get(name);
3676
+ if (parent && options) {
3677
+ if (parent.scope !== options.scope) {
3678
+ errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered with a "${options.scope}" scope.`));
3679
+ }
3680
+ if (parent.auto !== options.auto) {
3681
+ errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered as { auto: ${options.auto} }.`));
3682
+ }
3683
+ } else if (parent) {
3684
+ options = {
3685
+ auto: parent.auto,
3686
+ scope: parent.scope,
3687
+ injected: parent.injected
3688
+ };
3689
+ } else if (!options) {
3690
+ options = {
3691
+ auto: false,
3692
+ injected: false,
3693
+ scope: "test"
3694
+ };
3695
+ }
3696
+ if (options.scope && !TestFixtures._fixtureScopes.includes(options.scope)) {
3697
+ errors.push(new FixtureDependencyError(`The "${name}" fixture has unknown scope "${options.scope}".`));
3698
+ }
3699
+ if (!supportNonTest && options.scope !== "test") {
3700
+ errors.push(new FixtureDependencyError(`The "${name}" fixture cannot be defined with a ${options.scope} scope${!_options?.scope && parent?.scope ? " (inherited from the base fixture)" : ""} inside the describe block. Define it at the top level of the file instead.`));
3701
+ }
3702
+ const deps = isFixtureFunction(value) ? getUsedProps(value) : new Set();
3703
+ const item = {
3704
+ name,
3705
+ value,
3706
+ auto: options.auto ?? false,
3707
+ injected: options.injected ?? false,
3708
+ scope: options.scope ?? "test",
3709
+ deps,
3710
+ parent
3711
+ };
3712
+ registrations.set(name, item);
3713
+ if (item.scope === "worker" && (runner.pool === "vmThreads" || runner.pool === "vmForks")) {
3714
+ item.scope = "file";
3715
+ }
3716
+ });
3717
+ // validate fixture dependency scopes
3718
+ for (const fixture of registrations.values()){
3719
+ for (const depName of fixture.deps){
3720
+ if (TestFixtures._builtinFixtures.includes(depName)) {
3721
+ continue;
3722
+ }
3723
+ const dep = registrations.get(depName);
3724
+ if (!dep) {
3725
+ errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on unknown fixture "${depName}".`));
3726
+ continue;
3727
+ }
3728
+ if (depName === fixture.name && !fixture.parent) {
3729
+ errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on itself, but does not have a base implementation.`));
3730
+ continue;
3731
+ }
3732
+ if (TestFixtures._fixtureScopes.indexOf(fixture.scope) > TestFixtures._fixtureScopes.indexOf(dep.scope)) {
3733
+ errors.push(new FixtureDependencyError(`The ${fixture.scope} "${fixture.name}" fixture cannot depend on a ${dep.scope} fixture "${dep.name}".`));
3734
+ continue;
3735
+ }
3483
3736
  }
3484
3737
  }
3485
- });
3486
- return context;
3738
+ if (errors.length === 1) {
3739
+ throw errors[0];
3740
+ } else if (errors.length > 1) {
3741
+ throw new AggregateError(errors, "Cannot resolve user fixtures. See errors for more information.");
3742
+ }
3743
+ return registrations;
3744
+ }
3487
3745
  }
3488
- const fixtureValueMaps = new Map();
3489
- const cleanupFnArrayMap = new Map();
3490
- function withFixtures(runner, fn, testContext) {
3491
- return (hookContext)=>{
3492
- const context = hookContext || testContext;
3746
+ const cleanupFnArrayMap = new WeakMap();
3747
+ const contextHasFixturesCache = new WeakMap();
3748
+ function withFixtures(fn, options) {
3749
+ const collector = getCurrentSuite();
3750
+ const suite = options?.suite || collector.suite || collector.file;
3751
+ return async (hookContext)=>{
3752
+ const context = hookContext || options?.context;
3493
3753
  if (!context) {
3754
+ if (options?.suiteHook) {
3755
+ validateSuiteHook(fn, options.suiteHook, options.stackTraceError);
3756
+ }
3494
3757
  return fn({});
3495
3758
  }
3496
- const fixtures = getTestFixture(context);
3497
- if (!(fixtures === null || fixtures === void 0 ? void 0 : fixtures.length)) {
3759
+ const fixtures = options?.fixtures || getTestFixtures(context);
3760
+ if (!fixtures) {
3498
3761
  return fn(context);
3499
3762
  }
3500
- const usedProps = getUsedProps(fn);
3501
- const hasAutoFixture = fixtures.some(({ auto })=>auto);
3502
- if (!usedProps.length && !hasAutoFixture) {
3763
+ const registrations = fixtures.get(suite);
3764
+ if (!registrations.size) {
3503
3765
  return fn(context);
3504
3766
  }
3505
- if (!fixtureValueMaps.get(context)) {
3506
- fixtureValueMaps.set(context, new Map());
3767
+ const usedFixtures = [];
3768
+ const usedProps = getUsedProps(fn);
3769
+ for (const fixture of registrations.values()){
3770
+ if (fixture.auto || usedProps.has(fixture.name)) {
3771
+ usedFixtures.push(fixture);
3772
+ }
3773
+ }
3774
+ if (!usedFixtures.length) {
3775
+ return fn(context);
3507
3776
  }
3508
- const fixtureValueMap = fixtureValueMaps.get(context);
3509
3777
  if (!cleanupFnArrayMap.has(context)) {
3510
3778
  cleanupFnArrayMap.set(context, []);
3511
3779
  }
3512
3780
  const cleanupFnArray = cleanupFnArrayMap.get(context);
3513
- const usedFixtures = fixtures.filter(({ prop, auto })=>auto || usedProps.includes(prop));
3514
- const pendingFixtures = resolveDeps(usedFixtures);
3781
+ const pendingFixtures = resolveDeps(usedFixtures, registrations);
3515
3782
  if (!pendingFixtures.length) {
3516
3783
  return fn(context);
3517
3784
  }
3518
- async function resolveFixtures() {
3519
- for (const fixture of pendingFixtures){
3785
+ // Check if suite-level hook is trying to access test-scoped fixtures
3786
+ // Suite hooks (beforeAll/afterAll/aroundAll) can only access file/worker scoped fixtures
3787
+ if (options?.suiteHook) {
3788
+ const testScopedFixtures = pendingFixtures.filter((f)=>f.scope === "test");
3789
+ if (testScopedFixtures.length > 0) {
3790
+ const fixtureNames = testScopedFixtures.map((f)=>`"${f.name}"`).join(", ");
3791
+ const alternativeHook = {
3792
+ aroundAll: "aroundEach",
3793
+ beforeAll: "beforeEach",
3794
+ afterAll: "afterEach"
3795
+ };
3796
+ const error = new FixtureDependencyError(`Test-scoped fixtures cannot be used inside ${options.suiteHook} hook. ` + `The following fixtures are test-scoped: ${fixtureNames}. ` + `Use { scope: 'file' } or { scope: 'worker' } fixtures instead, or move the logic to ${alternativeHook[options.suiteHook]} hook.`);
3797
+ // Use stack trace from hook registration for better error location
3798
+ if (options.stackTraceError?.stack) {
3799
+ error.stack = error.message + options.stackTraceError.stack.replace(options.stackTraceError.message, "");
3800
+ }
3801
+ throw error;
3802
+ }
3803
+ }
3804
+ if (!contextHasFixturesCache.has(context)) {
3805
+ contextHasFixturesCache.set(context, new WeakSet());
3806
+ }
3807
+ const cachedFixtures = contextHasFixturesCache.get(context);
3808
+ for (const fixture of pendingFixtures){
3809
+ if (fixture.scope === "test") {
3520
3810
  // fixture could be already initialized during "before" hook
3521
- if (fixtureValueMap.has(fixture)) {
3811
+ // we can't check "fixture.name" in context because context may
3812
+ // access the parent fixture ({ a: ({ a }) => {} })
3813
+ if (cachedFixtures.has(fixture)) {
3522
3814
  continue;
3523
3815
  }
3524
- const resolvedValue = await resolveFixtureValue(runner, fixture, context, cleanupFnArray);
3525
- context[fixture.prop] = resolvedValue;
3526
- fixtureValueMap.set(fixture, resolvedValue);
3527
- if (fixture.scope === "test") {
3528
- cleanupFnArray.unshift(()=>{
3529
- fixtureValueMap.delete(fixture);
3530
- });
3531
- }
3816
+ cachedFixtures.add(fixture);
3817
+ const resolvedValue = await resolveTestFixtureValue(fixture, context, cleanupFnArray);
3818
+ context[fixture.name] = resolvedValue;
3819
+ cleanupFnArray.push(()=>{
3820
+ cachedFixtures.delete(fixture);
3821
+ });
3822
+ } else {
3823
+ const resolvedValue = await resolveScopeFixtureValue(fixtures, suite, fixture);
3824
+ context[fixture.name] = resolvedValue;
3532
3825
  }
3533
3826
  }
3534
- return resolveFixtures().then(()=>fn(context));
3827
+ return fn(context);
3535
3828
  };
3536
3829
  }
3537
- const globalFixturePromise = new WeakMap();
3538
- function resolveFixtureValue(runner, fixture, context, cleanupFnArray) {
3539
- var _runner$getWorkerCont;
3540
- const fileContext = getFileContext(context.task.file);
3541
- const workerContext = (_runner$getWorkerCont = runner.getWorkerContext) === null || _runner$getWorkerCont === void 0 ? void 0 : _runner$getWorkerCont.call(runner);
3542
- if (!fixture.isFn) {
3543
- var _fixture$prop;
3544
- fileContext[_fixture$prop = fixture.prop] ?? (fileContext[_fixture$prop] = fixture.value);
3545
- if (workerContext) {
3546
- var _fixture$prop2;
3547
- workerContext[_fixture$prop2 = fixture.prop] ?? (workerContext[_fixture$prop2] = fixture.value);
3548
- }
3830
+ function isFixtureFunction(value) {
3831
+ return typeof value === "function";
3832
+ }
3833
+ function resolveTestFixtureValue(fixture, context, cleanupFnArray) {
3834
+ if (!isFixtureFunction(fixture.value)) {
3549
3835
  return fixture.value;
3550
3836
  }
3551
- if (fixture.scope === "test") {
3552
- return resolveFixtureFunction(fixture.value, context, cleanupFnArray);
3553
- }
3554
- // in case the test runs in parallel
3555
- if (globalFixturePromise.has(fixture)) {
3556
- return globalFixturePromise.get(fixture);
3837
+ return resolveFixtureFunction(fixture.value, context, cleanupFnArray);
3838
+ }
3839
+ const scopedFixturePromiseCache = new WeakMap();
3840
+ async function resolveScopeFixtureValue(fixtures, suite, fixture) {
3841
+ const workerContext = fixtures.getWorkerContext();
3842
+ const fileContext = fixtures.getFileContext(suite.file);
3843
+ const fixtureContext = fixture.scope === "worker" ? workerContext : fileContext;
3844
+ if (!isFixtureFunction(fixture.value)) {
3845
+ fixtureContext[fixture.name] = fixture.value;
3846
+ return fixture.value;
3557
3847
  }
3558
- let fixtureContext;
3559
- if (fixture.scope === "worker") {
3560
- if (!workerContext) {
3561
- throw new TypeError("[@vitest/runner] The worker context is not available in the current test runner. Please, provide the `getWorkerContext` method when initiating the runner.");
3562
- }
3563
- fixtureContext = workerContext;
3564
- } else {
3565
- fixtureContext = fileContext;
3848
+ if (fixture.name in fixtureContext) {
3849
+ return fixtureContext[fixture.name];
3566
3850
  }
3567
- if (fixture.prop in fixtureContext) {
3568
- return fixtureContext[fixture.prop];
3851
+ if (scopedFixturePromiseCache.has(fixture)) {
3852
+ return scopedFixturePromiseCache.get(fixture);
3569
3853
  }
3570
3854
  if (!cleanupFnArrayMap.has(fixtureContext)) {
3571
3855
  cleanupFnArrayMap.set(fixtureContext, []);
3572
3856
  }
3573
3857
  const cleanupFnFileArray = cleanupFnArrayMap.get(fixtureContext);
3574
- const promise = resolveFixtureFunction(fixture.value, fixtureContext, cleanupFnFileArray).then((value)=>{
3575
- fixtureContext[fixture.prop] = value;
3576
- globalFixturePromise.delete(fixture);
3858
+ const promise = resolveFixtureFunction(fixture.value, fixture.scope === "file" ? {
3859
+ ...workerContext,
3860
+ ...fileContext
3861
+ } : fixtureContext, cleanupFnFileArray).then((value)=>{
3862
+ fixtureContext[fixture.name] = value;
3863
+ scopedFixturePromiseCache.delete(fixture);
3577
3864
  return value;
3578
3865
  });
3579
- globalFixturePromise.set(fixture, promise);
3866
+ scopedFixturePromiseCache.set(fixture, promise);
3580
3867
  return promise;
3581
3868
  }
3582
3869
  async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
@@ -3607,29 +3894,66 @@ async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
3607
3894
  });
3608
3895
  return useFnArgPromise;
3609
3896
  }
3610
- function resolveDeps(fixtures, depSet = new Set(), pendingFixtures = []) {
3611
- fixtures.forEach((fixture)=>{
3897
+ function resolveDeps(usedFixtures, registrations, depSet = new Set(), pendingFixtures = []) {
3898
+ usedFixtures.forEach((fixture)=>{
3612
3899
  if (pendingFixtures.includes(fixture)) {
3613
3900
  return;
3614
3901
  }
3615
- if (!fixture.isFn || !fixture.deps) {
3902
+ if (!isFixtureFunction(fixture.value) || !fixture.deps) {
3616
3903
  pendingFixtures.push(fixture);
3617
3904
  return;
3618
3905
  }
3619
3906
  if (depSet.has(fixture)) {
3620
- throw new Error(`Circular fixture dependency detected: ${fixture.prop} <- ${[
3621
- ...depSet
3622
- ].reverse().map((d)=>d.prop).join(" <- ")}`);
3907
+ if (fixture.parent) {
3908
+ fixture = fixture.parent;
3909
+ } else {
3910
+ throw new Error(`Circular fixture dependency detected: ${fixture.name} <- ${[
3911
+ ...depSet
3912
+ ].reverse().map((d)=>d.name).join(" <- ")}`);
3913
+ }
3623
3914
  }
3624
3915
  depSet.add(fixture);
3625
- resolveDeps(fixture.deps, depSet, pendingFixtures);
3916
+ resolveDeps([
3917
+ ...fixture.deps
3918
+ ].map((n)=>n === fixture.name ? fixture.parent : registrations.get(n)).filter((n)=>!!n), registrations, depSet, pendingFixtures);
3626
3919
  pendingFixtures.push(fixture);
3627
3920
  depSet.clear();
3628
3921
  });
3629
3922
  return pendingFixtures;
3630
3923
  }
3631
- function getUsedProps(fn) {
3632
- let fnString = filterOutComments(fn.toString());
3924
+ function validateSuiteHook(fn, hook, suiteError) {
3925
+ const usedProps = getUsedProps(fn, {
3926
+ sourceError: suiteError,
3927
+ suiteHook: hook
3928
+ });
3929
+ if (usedProps.size) {
3930
+ const error = new FixtureAccessError(`The ${hook} hook uses fixtures "${[
3931
+ ...usedProps
3932
+ ].join("\", \"")}", but has no access to context. ` + `Did you forget to call it as "test.${hook}()" instead of "${hook}()"?\n` + `If you used internal "suite" task as the first argument previously, access it in the second argument instead. ` + `See https://vitest.dev/guide/test-context#suite-level-hooks`);
3933
+ if (suiteError) {
3934
+ error.stack = suiteError.stack?.replace(suiteError.message, error.message);
3935
+ }
3936
+ throw error;
3937
+ }
3938
+ }
3939
+ const kPropsSymbol = Symbol("$vitest:fixture-props");
3940
+ const kPropNamesSymbol = Symbol("$vitest:fixture-prop-names");
3941
+ function configureProps(fn, options) {
3942
+ Object.defineProperty(fn, kPropsSymbol, {
3943
+ value: options,
3944
+ enumerable: false
3945
+ });
3946
+ }
3947
+ function memoProps(fn, props) {
3948
+ fn[kPropNamesSymbol] = props;
3949
+ return props;
3950
+ }
3951
+ function getUsedProps(fn, { sourceError, suiteHook } = {}) {
3952
+ if (kPropNamesSymbol in fn) {
3953
+ return fn[kPropNamesSymbol];
3954
+ }
3955
+ const { index: fixturesIndex = 0, original: implementation = fn } = kPropsSymbol in fn ? fn[kPropsSymbol] : {};
3956
+ let fnString = filterOutComments(implementation.toString());
3633
3957
  // match lowered async function and strip it off
3634
3958
  // example code on esbuild-try https://esbuild.github.io/try/#YgAwLjI0LjAALS1zdXBwb3J0ZWQ6YXN5bmMtYXdhaXQ9ZmFsc2UAZQBlbnRyeS50cwBjb25zdCBvID0gewogIGYxOiBhc3luYyAoKSA9PiB7fSwKICBmMjogYXN5bmMgKGEpID0+IHt9LAogIGYzOiBhc3luYyAoYSwgYikgPT4ge30sCiAgZjQ6IGFzeW5jIGZ1bmN0aW9uKGEpIHt9LAogIGY1OiBhc3luYyBmdW5jdGlvbiBmZihhKSB7fSwKICBhc3luYyBmNihhKSB7fSwKCiAgZzE6IGFzeW5jICgpID0+IHt9LAogIGcyOiBhc3luYyAoeyBhIH0pID0+IHt9LAogIGczOiBhc3luYyAoeyBhIH0sIGIpID0+IHt9LAogIGc0OiBhc3luYyBmdW5jdGlvbiAoeyBhIH0pIHt9LAogIGc1OiBhc3luYyBmdW5jdGlvbiBnZyh7IGEgfSkge30sCiAgYXN5bmMgZzYoeyBhIH0pIHt9LAoKICBoMTogYXN5bmMgKCkgPT4ge30sCiAgLy8gY29tbWVudCBiZXR3ZWVuCiAgaDI6IGFzeW5jIChhKSA9PiB7fSwKfQ
3635
3959
  // __async(this, null, function*
@@ -3640,56 +3964,37 @@ function getUsedProps(fn) {
3640
3964
  }
3641
3965
  const match = fnString.match(/[^(]*\(([^)]*)/);
3642
3966
  if (!match) {
3643
- return [];
3967
+ return memoProps(fn, new Set());
3644
3968
  }
3645
3969
  const args = splitByComma(match[1]);
3646
3970
  if (!args.length) {
3647
- return [];
3971
+ return memoProps(fn, new Set());
3648
3972
  }
3649
- let first = args[0];
3650
- if ("__VITEST_FIXTURE_INDEX__" in fn) {
3651
- first = args[fn.__VITEST_FIXTURE_INDEX__];
3652
- if (!first) {
3653
- return [];
3654
- }
3973
+ const fixturesArgument = args[fixturesIndex];
3974
+ if (!fixturesArgument) {
3975
+ return memoProps(fn, new Set());
3655
3976
  }
3656
- if (!(first[0] === "{" && first.endsWith("}"))) {
3657
- throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${first}".`);
3977
+ if (!(fixturesArgument[0] === "{" && fixturesArgument.endsWith("}"))) {
3978
+ const ordinalArgument = ordinal(fixturesIndex + 1);
3979
+ const error = new FixtureParseError(`The ${ordinalArgument} argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). ` + `Instead, received "${fixturesArgument}".` + `${suiteHook ? ` If you used internal "suite" task as the ${ordinalArgument} argument previously, access it in the ${ordinal(fixturesIndex + 2)} argument instead.` : ""}`);
3980
+ if (sourceError) {
3981
+ error.stack = sourceError.stack?.replace(sourceError.message, error.message);
3982
+ }
3983
+ throw error;
3658
3984
  }
3659
- const _first = first.slice(1, -1).replace(/\s/g, "");
3985
+ const _first = fixturesArgument.slice(1, -1).replace(/\s/g, "");
3660
3986
  const props = splitByComma(_first).map((prop)=>{
3661
3987
  return prop.replace(/:.*|=.*/g, "");
3662
3988
  });
3663
3989
  const last = props.at(-1);
3664
3990
  if (last && last.startsWith("...")) {
3665
- throw new Error(`Rest parameters are not supported in fixtures, received "${last}".`);
3666
- }
3667
- return props;
3668
- }
3669
- function filterOutComments(s) {
3670
- const result = [];
3671
- let commentState = "none";
3672
- for(let i = 0; i < s.length; ++i){
3673
- if (commentState === "singleline") {
3674
- if (s[i] === "\n") {
3675
- commentState = "none";
3676
- }
3677
- } else if (commentState === "multiline") {
3678
- if (s[i - 1] === "*" && s[i] === "/") {
3679
- commentState = "none";
3680
- }
3681
- } else if (commentState === "none") {
3682
- if (s[i] === "/" && s[i + 1] === "/") {
3683
- commentState = "singleline";
3684
- } else if (s[i] === "/" && s[i + 1] === "*") {
3685
- commentState = "multiline";
3686
- i += 2;
3687
- } else {
3688
- result.push(s[i]);
3689
- }
3991
+ const error = new FixtureParseError(`Rest parameters are not supported in fixtures, received "${last}".`);
3992
+ if (sourceError) {
3993
+ error.stack = sourceError.stack?.replace(sourceError.message, error.message);
3690
3994
  }
3995
+ throw error;
3691
3996
  }
3692
- return result.join("");
3997
+ return memoProps(fn, new Set(props));
3693
3998
  }
3694
3999
  function splitByComma(s) {
3695
4000
  const result = [];
@@ -3719,6 +4024,8 @@ function getDefaultHookTimeout() {
3719
4024
  }
3720
4025
  const CLEANUP_TIMEOUT_KEY = Symbol.for("VITEST_CLEANUP_TIMEOUT");
3721
4026
  const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
4027
+ const AROUND_TIMEOUT_KEY = Symbol.for("VITEST_AROUND_TIMEOUT");
4028
+ const AROUND_STACK_TRACE_KEY = Symbol.for("VITEST_AROUND_STACK_TRACE");
3722
4029
  /**
3723
4030
  * Registers a callback function to be executed once before all tests within the current suite.
3724
4031
  * This hook is useful for scenarios where you need to perform setup operations that are common to all tests in a suite, such as initializing a database connection or setting up a test environment.
@@ -3740,7 +4047,8 @@ const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
3740
4047
  "function"
3741
4048
  ]);
3742
4049
  const stackTraceError = new Error("STACK_TRACE_ERROR");
3743
- return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(fn, timeout, true, stackTraceError), {
4050
+ const context = getChainableContext(this);
4051
+ return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(withSuiteFixtures("beforeAll", fn, context, stackTraceError), timeout, true, stackTraceError), {
3744
4052
  [CLEANUP_TIMEOUT_KEY]: timeout,
3745
4053
  [CLEANUP_STACK_TRACE_KEY]: stackTraceError
3746
4054
  }));
@@ -3765,7 +4073,9 @@ const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
3765
4073
  assertTypes(fn, "\"afterAll\" callback", [
3766
4074
  "function"
3767
4075
  ]);
3768
- return getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
4076
+ const context = getChainableContext(this);
4077
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
4078
+ return getCurrentSuite().on("afterAll", withTimeout(withSuiteFixtures("afterAll", fn, context, stackTraceError), timeout ?? getDefaultHookTimeout(), true, stackTraceError));
3769
4079
  }
3770
4080
  /**
3771
4081
  * Registers a callback function to be executed before each test within the current suite.
@@ -3788,8 +4098,13 @@ const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
3788
4098
  "function"
3789
4099
  ]);
3790
4100
  const stackTraceError = new Error("STACK_TRACE_ERROR");
3791
- const runner = getRunner();
3792
- return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
4101
+ const wrapper = (context, suite)=>{
4102
+ const fixtureResolver = withFixtures(fn, {
4103
+ suite
4104
+ });
4105
+ return fixtureResolver(context);
4106
+ };
4107
+ return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(wrapper, timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
3793
4108
  [CLEANUP_TIMEOUT_KEY]: timeout,
3794
4109
  [CLEANUP_STACK_TRACE_KEY]: stackTraceError
3795
4110
  }));
@@ -3814,8 +4129,118 @@ const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
3814
4129
  assertTypes(fn, "\"afterEach\" callback", [
3815
4130
  "function"
3816
4131
  ]);
3817
- const runner = getRunner();
3818
- return getCurrentSuite().on("afterEach", withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
4132
+ const wrapper = (context, suite)=>{
4133
+ const fixtureResolver = withFixtures(fn, {
4134
+ suite
4135
+ });
4136
+ return fixtureResolver(context);
4137
+ };
4138
+ return getCurrentSuite().on("afterEach", withTimeout(wrapper, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
4139
+ }
4140
+ /**
4141
+ * Registers a callback function that wraps around all tests within the current suite.
4142
+ * The callback receives a `runSuite` function that must be called to run the suite's tests.
4143
+ * This hook is useful for scenarios where you need to wrap an entire suite in a context
4144
+ * (e.g., starting a server, opening a database connection that all tests share).
4145
+ *
4146
+ * **Note:** When multiple `aroundAll` hooks are registered, they are nested inside each other.
4147
+ * The first registered hook is the outermost wrapper.
4148
+ *
4149
+ * @param {Function} fn - The callback function that wraps the suite. Must call `runSuite()` to run the tests.
4150
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
4151
+ * @returns {void}
4152
+ * @example
4153
+ * ```ts
4154
+ * // Example of using aroundAll to wrap suite in a tracing span
4155
+ * aroundAll(async (runSuite) => {
4156
+ * await tracer.trace('test-suite', runSuite);
4157
+ * });
4158
+ * ```
4159
+ * @example
4160
+ * ```ts
4161
+ * // Example of using aroundAll with fixtures
4162
+ * aroundAll(async (runSuite, { db }) => {
4163
+ * await db.transaction(() => runSuite());
4164
+ * });
4165
+ * ```
4166
+ */ function aroundAll(fn, timeout) {
4167
+ assertTypes(fn, "\"aroundAll\" callback", [
4168
+ "function"
4169
+ ]);
4170
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
4171
+ const resolvedTimeout = timeout ?? getDefaultHookTimeout();
4172
+ const context = getChainableContext(this);
4173
+ return getCurrentSuite().on("aroundAll", Object.assign(withSuiteFixtures("aroundAll", fn, context, stackTraceError, 1), {
4174
+ [AROUND_TIMEOUT_KEY]: resolvedTimeout,
4175
+ [AROUND_STACK_TRACE_KEY]: stackTraceError
4176
+ }));
4177
+ }
4178
+ /**
4179
+ * Registers a callback function that wraps around each test within the current suite.
4180
+ * The callback receives a `runTest` function that must be called to run the test.
4181
+ * This hook is useful for scenarios where you need to wrap tests in a context (e.g., database transactions).
4182
+ *
4183
+ * **Note:** When multiple `aroundEach` hooks are registered, they are nested inside each other.
4184
+ * The first registered hook is the outermost wrapper.
4185
+ *
4186
+ * @param {Function} fn - The callback function that wraps the test. Must call `runTest()` to run the test.
4187
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
4188
+ * @returns {void}
4189
+ * @example
4190
+ * ```ts
4191
+ * // Example of using aroundEach to wrap tests in a database transaction
4192
+ * aroundEach(async (runTest) => {
4193
+ * await database.transaction(() => runTest());
4194
+ * });
4195
+ * ```
4196
+ * @example
4197
+ * ```ts
4198
+ * // Example of using aroundEach with fixtures
4199
+ * aroundEach(async (runTest, { db }) => {
4200
+ * await db.transaction(() => runTest());
4201
+ * });
4202
+ * ```
4203
+ */ function aroundEach(fn, timeout) {
4204
+ assertTypes(fn, "\"aroundEach\" callback", [
4205
+ "function"
4206
+ ]);
4207
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
4208
+ const resolvedTimeout = timeout ?? getDefaultHookTimeout();
4209
+ const wrapper = (runTest, context, suite)=>{
4210
+ const innerFn = (ctx)=>fn(runTest, ctx, suite);
4211
+ configureProps(innerFn, {
4212
+ index: 1,
4213
+ original: fn
4214
+ });
4215
+ const fixtureResolver = withFixtures(innerFn, {
4216
+ suite
4217
+ });
4218
+ return fixtureResolver(context);
4219
+ };
4220
+ return getCurrentSuite().on("aroundEach", Object.assign(wrapper, {
4221
+ [AROUND_TIMEOUT_KEY]: resolvedTimeout,
4222
+ [AROUND_STACK_TRACE_KEY]: stackTraceError
4223
+ }));
4224
+ }
4225
+ function withSuiteFixtures(suiteHook, fn, context, stackTraceError, contextIndex = 0) {
4226
+ return (...args)=>{
4227
+ const suite = args.at(-1);
4228
+ const prefix = args.slice(0, -1);
4229
+ const wrapper = (ctx)=>fn(...prefix, ctx, suite);
4230
+ configureProps(wrapper, {
4231
+ index: contextIndex,
4232
+ original: fn
4233
+ });
4234
+ const fixtures = context?.getFixtures();
4235
+ const fileContext = fixtures?.getFileContext(suite.file);
4236
+ const fixtured = withFixtures(wrapper, {
4237
+ suiteHook,
4238
+ fixtures,
4239
+ context: fileContext,
4240
+ stackTraceError
4241
+ });
4242
+ return fixtured();
4243
+ };
3819
4244
  }
3820
4245
  /**
3821
4246
  * Creates a suite of tests, allowing for grouping and hierarchical organization of tests.
@@ -3959,9 +4384,12 @@ function createSuiteHooks() {
3959
4384
  beforeAll: [],
3960
4385
  afterAll: [],
3961
4386
  beforeEach: [],
3962
- afterEach: []
4387
+ afterEach: [],
4388
+ aroundEach: [],
4389
+ aroundAll: []
3963
4390
  };
3964
4391
  }
4392
+ const POSITIVE_INFINITY = Number.POSITIVE_INFINITY;
3965
4393
  function parseArguments(optionsOrFn, timeoutOrTest) {
3966
4394
  if (timeoutOrTest != null && typeof timeoutOrTest === "object") {
3967
4395
  throw new TypeError(`Signature "test(name, fn, { ... })" was deprecated in Vitest 3 and removed in Vitest 4. Please, provide options as a second argument instead.`);
@@ -3990,23 +4418,59 @@ function parseArguments(optionsOrFn, timeoutOrTest) {
3990
4418
  };
3991
4419
  }
3992
4420
  // implementations
3993
- function createSuiteCollector(name, factory = ()=>{}, mode, each, suiteOptions, parentCollectorFixtures) {
4421
+ function createSuiteCollector(name, factory = ()=>{}, mode, each, suiteOptions) {
3994
4422
  const tasks = [];
3995
4423
  let suite;
3996
4424
  initSuite();
3997
4425
  const task = function(name = "", options = {}) {
3998
- var _collectorContext$cur, _collectorContext$cur2, _collectorContext$cur3;
3999
- const timeout = (options === null || options === void 0 ? void 0 : options.timeout) ?? runner.config.testTimeout;
4000
- const currentSuite = (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.suite;
4426
+ const currentSuite = collectorContext.currentSuite?.suite;
4427
+ const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
4428
+ const parentTags = parentTask?.tags || [];
4429
+ const testTags = unique([
4430
+ ...parentTags,
4431
+ ...toArray(options.tags)
4432
+ ]);
4433
+ const tagsOptions = testTags.map((tag)=>{
4434
+ const tagDefinition = runner.config.tags?.find((t)=>t.name === tag);
4435
+ if (!tagDefinition && runner.config.strictTags) {
4436
+ throw createNoTagsError(runner.config.tags, tag);
4437
+ }
4438
+ return tagDefinition;
4439
+ }).filter((r)=>r != null).sort((tag1, tag2)=>(tag2.priority ?? POSITIVE_INFINITY) - (tag1.priority ?? POSITIVE_INFINITY)).reduce((acc, tag)=>{
4440
+ const { name, description, priority, meta, ...options } = tag;
4441
+ Object.assign(acc, options);
4442
+ if (meta) {
4443
+ acc.meta = Object.assign(acc.meta ?? Object.create(null), meta);
4444
+ }
4445
+ return acc;
4446
+ }, {});
4447
+ const testOwnMeta = options.meta;
4448
+ options = {
4449
+ ...tagsOptions,
4450
+ ...options
4451
+ };
4452
+ const timeout = options.timeout ?? runner.config.testTimeout;
4453
+ const parentMeta = currentSuite?.meta;
4454
+ const tagMeta = tagsOptions.meta;
4455
+ const testMeta = Object.create(null);
4456
+ if (tagMeta) {
4457
+ Object.assign(testMeta, tagMeta);
4458
+ }
4459
+ if (parentMeta) {
4460
+ Object.assign(testMeta, parentMeta);
4461
+ }
4462
+ if (testOwnMeta) {
4463
+ Object.assign(testMeta, testOwnMeta);
4464
+ }
4001
4465
  const task = {
4002
4466
  id: "",
4003
4467
  name,
4004
4468
  fullName: createTaskName([
4005
- (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullName) ?? ((_collectorContext$cur2 = collectorContext.currentSuite) === null || _collectorContext$cur2 === void 0 || (_collectorContext$cur2 = _collectorContext$cur2.file) === null || _collectorContext$cur2 === void 0 ? void 0 : _collectorContext$cur2.fullName),
4469
+ currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName,
4006
4470
  name
4007
4471
  ]),
4008
4472
  fullTestName: createTaskName([
4009
- currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullTestName,
4473
+ currentSuite?.fullTestName,
4010
4474
  name
4011
4475
  ]),
4012
4476
  suite: currentSuite,
@@ -4014,14 +4478,15 @@ function createSuiteCollector(name, factory = ()=>{}, mode, each, suiteOptions,
4014
4478
  fails: options.fails,
4015
4479
  context: undefined,
4016
4480
  type: "test",
4017
- file: (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.file) ?? ((_collectorContext$cur3 = collectorContext.currentSuite) === null || _collectorContext$cur3 === void 0 ? void 0 : _collectorContext$cur3.file),
4481
+ file: currentSuite?.file ?? collectorContext.currentSuite?.file,
4018
4482
  timeout,
4019
4483
  retry: options.retry ?? runner.config.retry,
4020
4484
  repeats: options.repeats,
4021
4485
  mode: options.only ? "only" : options.skip ? "skip" : options.todo ? "todo" : "run",
4022
- meta: options.meta ?? Object.create(null),
4486
+ meta: testMeta,
4023
4487
  annotations: [],
4024
- artifacts: []
4488
+ artifacts: [],
4489
+ tags: testTags
4025
4490
  };
4026
4491
  const handler = options.handler;
4027
4492
  if (task.mode === "run" && !handler) {
@@ -4030,21 +4495,22 @@ function createSuiteCollector(name, factory = ()=>{}, mode, each, suiteOptions,
4030
4495
  if (options.concurrent || !options.sequential && runner.config.sequence.concurrent) {
4031
4496
  task.concurrent = true;
4032
4497
  }
4033
- task.shuffle = suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle;
4498
+ task.shuffle = suiteOptions?.shuffle;
4034
4499
  const context = createTestContext(task, runner);
4035
4500
  // create test context
4036
4501
  Object.defineProperty(task, "context", {
4037
4502
  value: context,
4038
4503
  enumerable: false
4039
4504
  });
4040
- setTestFixture(context, options.fixtures);
4041
- // custom can be called from any place, let's assume the limit is 15 stacks
4505
+ setTestFixture(context, options.fixtures ?? new TestFixtures());
4042
4506
  const limit = Error.stackTraceLimit;
4043
- Error.stackTraceLimit = 15;
4507
+ Error.stackTraceLimit = 10;
4044
4508
  const stackTraceError = new Error("STACK_TRACE_ERROR");
4045
4509
  Error.stackTraceLimit = limit;
4046
4510
  if (handler) {
4047
- setFn(task, withTimeout(withAwaitAsyncAssertions(withFixtures(runner, handler, context), task), timeout, false, stackTraceError, (_, error)=>abortIfTimeout([
4511
+ setFn(task, withTimeout(withCancel(withAwaitAsyncAssertions(withFixtures(handler, {
4512
+ context
4513
+ }), task), task.context.signal), timeout, false, stackTraceError, (_, error)=>abortIfTimeout([
4048
4514
  context
4049
4515
  ], error)));
4050
4516
  }
@@ -4068,8 +4534,14 @@ function createSuiteCollector(name, factory = ()=>{}, mode, each, suiteOptions,
4068
4534
  options = Object.assign({}, suiteOptions, options);
4069
4535
  }
4070
4536
  // inherit concurrent / sequential from suite
4071
- options.concurrent = this.concurrent || !this.sequential && (options === null || options === void 0 ? void 0 : options.concurrent);
4072
- options.sequential = this.sequential || !this.concurrent && (options === null || options === void 0 ? void 0 : options.sequential);
4537
+ const concurrent = this.concurrent ?? (!this.sequential && options?.concurrent);
4538
+ if (options.concurrent != null && concurrent != null) {
4539
+ options.concurrent = concurrent;
4540
+ }
4541
+ const sequential = this.sequential ?? (!this.concurrent && options?.sequential);
4542
+ if (options.sequential != null && sequential != null) {
4543
+ options.sequential = sequential;
4544
+ }
4073
4545
  const test = task(formatName(name), {
4074
4546
  ...this,
4075
4547
  ...options,
@@ -4077,7 +4549,6 @@ function createSuiteCollector(name, factory = ()=>{}, mode, each, suiteOptions,
4077
4549
  });
4078
4550
  test.type = "test";
4079
4551
  });
4080
- let collectorFixtures = parentCollectorFixtures;
4081
4552
  const collector = {
4082
4553
  type: "collector",
4083
4554
  name,
@@ -4085,54 +4556,50 @@ function createSuiteCollector(name, factory = ()=>{}, mode, each, suiteOptions,
4085
4556
  suite,
4086
4557
  options: suiteOptions,
4087
4558
  test,
4559
+ file: suite.file,
4088
4560
  tasks,
4089
4561
  collect,
4090
4562
  task,
4091
4563
  clear,
4092
- on: addHook,
4093
- fixtures () {
4094
- return collectorFixtures;
4095
- },
4096
- scoped (fixtures) {
4097
- const parsed = mergeContextFixtures(fixtures, {
4098
- fixtures: collectorFixtures
4099
- }, runner);
4100
- if (parsed.fixtures) {
4101
- collectorFixtures = parsed.fixtures;
4102
- }
4103
- }
4564
+ on: addHook
4104
4565
  };
4105
4566
  function addHook(name, ...fn) {
4106
4567
  getHooks(suite)[name].push(...fn);
4107
4568
  }
4108
4569
  function initSuite(includeLocation) {
4109
- var _collectorContext$cur4, _collectorContext$cur5, _collectorContext$cur6;
4110
4570
  if (typeof suiteOptions === "number") {
4111
4571
  suiteOptions = {
4112
4572
  timeout: suiteOptions
4113
4573
  };
4114
4574
  }
4115
- const currentSuite = (_collectorContext$cur4 = collectorContext.currentSuite) === null || _collectorContext$cur4 === void 0 ? void 0 : _collectorContext$cur4.suite;
4575
+ const currentSuite = collectorContext.currentSuite?.suite;
4576
+ const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
4577
+ const suiteTags = toArray(suiteOptions?.tags);
4578
+ validateTags(runner.config, suiteTags);
4116
4579
  suite = {
4117
4580
  id: "",
4118
4581
  type: "suite",
4119
4582
  name,
4120
4583
  fullName: createTaskName([
4121
- (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullName) ?? ((_collectorContext$cur5 = collectorContext.currentSuite) === null || _collectorContext$cur5 === void 0 || (_collectorContext$cur5 = _collectorContext$cur5.file) === null || _collectorContext$cur5 === void 0 ? void 0 : _collectorContext$cur5.fullName),
4584
+ currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName,
4122
4585
  name
4123
4586
  ]),
4124
4587
  fullTestName: createTaskName([
4125
- currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullTestName,
4588
+ currentSuite?.fullTestName,
4126
4589
  name
4127
4590
  ]),
4128
4591
  suite: currentSuite,
4129
4592
  mode,
4130
4593
  each,
4131
- file: (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.file) ?? ((_collectorContext$cur6 = collectorContext.currentSuite) === null || _collectorContext$cur6 === void 0 ? void 0 : _collectorContext$cur6.file),
4132
- shuffle: suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle,
4594
+ file: currentSuite?.file ?? collectorContext.currentSuite?.file,
4595
+ shuffle: suiteOptions?.shuffle,
4133
4596
  tasks: [],
4134
- meta: Object.create(null),
4135
- concurrent: suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.concurrent
4597
+ meta: suiteOptions?.meta ?? Object.create(null),
4598
+ concurrent: suiteOptions?.concurrent,
4599
+ tags: unique([
4600
+ ...parentTask?.tags || [],
4601
+ ...suiteTags
4602
+ ])
4136
4603
  };
4137
4604
  setHooks(suite, createSuiteHooks());
4138
4605
  }
@@ -4173,31 +4640,43 @@ function withAwaitAsyncAssertions(fn, task) {
4173
4640
  }
4174
4641
  function createSuite() {
4175
4642
  function suiteFn(name, factoryOrOptions, optionsOrFactory) {
4176
- var _currentSuite$options;
4177
- let mode = this.only ? "only" : this.skip ? "skip" : this.todo ? "todo" : "run";
4178
4643
  const currentSuite = collectorContext.currentSuite || defaultSuite;
4179
4644
  let { options, handler: factory } = parseArguments(factoryOrOptions, optionsOrFactory);
4180
- if (mode === "run" && !factory) {
4181
- mode = "todo";
4182
- }
4183
4645
  const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false;
4184
4646
  const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false;
4647
+ const { meta: parentMeta, ...parentOptions } = currentSuite?.options || {};
4185
4648
  // inherit options from current suite
4186
4649
  options = {
4187
- ...currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.options,
4188
- ...options,
4189
- shuffle: this.shuffle ?? options.shuffle ?? (currentSuite === null || currentSuite === void 0 || (_currentSuite$options = currentSuite.options) === null || _currentSuite$options === void 0 ? void 0 : _currentSuite$options.shuffle) ?? (void 0 )
4650
+ ...parentOptions,
4651
+ ...options
4190
4652
  };
4653
+ const shuffle = this.shuffle ?? options.shuffle ?? currentSuite?.options?.shuffle ?? runner?.config.sequence.shuffle;
4654
+ if (shuffle != null) {
4655
+ options.shuffle = shuffle;
4656
+ }
4657
+ let mode = this.only ?? options.only ? "only" : this.skip ?? options.skip ? "skip" : this.todo ?? options.todo ? "todo" : "run";
4658
+ // passed as test(name), assume it's a "todo"
4659
+ if (mode === "run" && !factory) {
4660
+ mode = "todo";
4661
+ }
4191
4662
  // inherit concurrent / sequential from suite
4192
4663
  const isConcurrent = isConcurrentSpecified || options.concurrent && !isSequentialSpecified;
4193
4664
  const isSequential = isSequentialSpecified || options.sequential && !isConcurrentSpecified;
4194
- options.concurrent = isConcurrent && !isSequential;
4195
- options.sequential = isSequential && !isConcurrent;
4196
- return createSuiteCollector(formatName(name), factory, mode, this.each, options, currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fixtures());
4665
+ if (isConcurrent != null) {
4666
+ options.concurrent = isConcurrent && !isSequential;
4667
+ }
4668
+ if (isSequential != null) {
4669
+ options.sequential = isSequential && !isConcurrent;
4670
+ }
4671
+ if (parentMeta) {
4672
+ options.meta = Object.assign(Object.create(null), parentMeta, options.meta);
4673
+ }
4674
+ return createSuiteCollector(formatName(name), factory, mode, this.each, options);
4197
4675
  }
4198
4676
  suiteFn.each = function(cases, ...args) {
4199
- const suite = this.withContext();
4200
- this.setContext("each", true);
4677
+ const context = getChainableContext(this);
4678
+ const suite = context.withContext();
4679
+ context.setContext("each", true);
4201
4680
  if (Array.isArray(cases) && args.length) {
4202
4681
  cases = formatTemplateString(cases, args);
4203
4682
  }
@@ -4224,7 +4703,7 @@ function createSuite() {
4224
4703
  }
4225
4704
  }
4226
4705
  });
4227
- this.setContext("each", undefined);
4706
+ context.setContext("each", undefined);
4228
4707
  };
4229
4708
  };
4230
4709
  suiteFn.for = function(cases, ...args) {
@@ -4250,11 +4729,12 @@ function createSuite() {
4250
4729
  "todo"
4251
4730
  ], suiteFn);
4252
4731
  }
4253
- function createTaskCollector(fn, context) {
4732
+ function createTaskCollector(fn) {
4254
4733
  const taskFn = fn;
4255
4734
  taskFn.each = function(cases, ...args) {
4256
- const test = this.withContext();
4257
- this.setContext("each", true);
4735
+ const context = getChainableContext(this);
4736
+ const test = context.withContext();
4737
+ context.setContext("each", true);
4258
4738
  if (Array.isArray(cases) && args.length) {
4259
4739
  cases = formatTemplateString(cases, args);
4260
4740
  }
@@ -4281,11 +4761,12 @@ function createTaskCollector(fn, context) {
4281
4761
  }
4282
4762
  }
4283
4763
  });
4284
- this.setContext("each", undefined);
4764
+ context.setContext("each", undefined);
4285
4765
  };
4286
4766
  };
4287
4767
  taskFn.for = function(cases, ...args) {
4288
- const test = this.withContext();
4768
+ const context = getChainableContext(this);
4769
+ const test = context.withContext();
4289
4770
  if (Array.isArray(cases) && args.length) {
4290
4771
  cases = formatTemplateString(cases, args);
4291
4772
  }
@@ -4296,8 +4777,10 @@ function createTaskCollector(fn, context) {
4296
4777
  // monkey-patch handler to allow parsing fixture
4297
4778
  const handlerWrapper = handler ? (ctx)=>handler(item, ctx) : undefined;
4298
4779
  if (handlerWrapper) {
4299
- handlerWrapper.__VITEST_FIXTURE_INDEX__ = 1;
4300
- handlerWrapper.toString = ()=>handler.toString();
4780
+ configureProps(handlerWrapper, {
4781
+ index: 1,
4782
+ original: handler
4783
+ });
4301
4784
  }
4302
4785
  test(formatTitle(_name, toArray(item), idx), options, handlerWrapper);
4303
4786
  });
@@ -4309,29 +4792,110 @@ function createTaskCollector(fn, context) {
4309
4792
  taskFn.runIf = function(condition) {
4310
4793
  return condition ? this : this.skip;
4311
4794
  };
4312
- taskFn.scoped = function(fixtures) {
4313
- const collector = getCurrentSuite();
4314
- collector.scoped(fixtures);
4315
- };
4316
- taskFn.extend = function(fixtures) {
4317
- const _context = mergeContextFixtures(fixtures, context || {}, runner);
4318
- const originalWrapper = fn;
4319
- return createTest(function(name, optionsOrFn, optionsOrTest) {
4320
- const collector = getCurrentSuite();
4321
- const scopedFixtures = collector.fixtures();
4322
- const context = {
4323
- ...this
4795
+ /**
4796
+ * Parse builder pattern arguments into a fixtures object.
4797
+ * Handles both builder pattern (name, options?, value) and object syntax.
4798
+ */ function parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn) {
4799
+ // Object syntax: just return as-is
4800
+ if (typeof fixturesOrName !== "string") {
4801
+ return fixturesOrName;
4802
+ }
4803
+ const fixtureName = fixturesOrName;
4804
+ let fixtureOptions;
4805
+ let fixtureValue;
4806
+ if (maybeFn !== undefined) {
4807
+ // (name, options, value) or (name, options, fn)
4808
+ fixtureOptions = optionsOrFn;
4809
+ fixtureValue = maybeFn;
4810
+ } else {
4811
+ // (name, value) or (name, fn)
4812
+ // Check if optionsOrFn looks like fixture options (has scope or auto)
4813
+ if (optionsOrFn !== null && typeof optionsOrFn === "object" && !Array.isArray(optionsOrFn) && ("scope" in optionsOrFn || "auto" in optionsOrFn)) {
4814
+ // (name, options) with no value - treat as empty object fixture
4815
+ fixtureOptions = optionsOrFn;
4816
+ fixtureValue = {};
4817
+ } else {
4818
+ // (name, value) or (name, fn)
4819
+ fixtureOptions = undefined;
4820
+ fixtureValue = optionsOrFn;
4821
+ }
4822
+ }
4823
+ // Function value: wrap with onCleanup pattern
4824
+ if (typeof fixtureValue === "function") {
4825
+ const builderFn = fixtureValue;
4826
+ // Wrap builder pattern function (returns value) to use() pattern
4827
+ const fixture = async (ctx, use)=>{
4828
+ let cleanup;
4829
+ const onCleanup = (fn)=>{
4830
+ if (cleanup !== undefined) {
4831
+ throw new Error(`onCleanup can only be called once per fixture. ` + `Define separate fixtures if you need multiple cleanup functions.`);
4832
+ }
4833
+ cleanup = fn;
4834
+ };
4835
+ const value = await builderFn(ctx, {
4836
+ onCleanup
4837
+ });
4838
+ await use(value);
4839
+ if (cleanup) {
4840
+ await cleanup();
4841
+ }
4324
4842
  };
4325
- if (scopedFixtures) {
4326
- context.fixtures = mergeScopedFixtures(context.fixtures || [], scopedFixtures);
4843
+ configureProps(fixture, {
4844
+ original: builderFn
4845
+ });
4846
+ if (fixtureOptions) {
4847
+ return {
4848
+ [fixtureName]: [
4849
+ fixture,
4850
+ fixtureOptions
4851
+ ]
4852
+ };
4327
4853
  }
4328
- originalWrapper.call(context, formatName(name), optionsOrFn, optionsOrTest);
4329
- }, _context);
4854
+ return {
4855
+ [fixtureName]: fixture
4856
+ };
4857
+ }
4858
+ // Non-function value: use directly
4859
+ if (fixtureOptions) {
4860
+ return {
4861
+ [fixtureName]: [
4862
+ fixtureValue,
4863
+ fixtureOptions
4864
+ ]
4865
+ };
4866
+ }
4867
+ return {
4868
+ [fixtureName]: fixtureValue
4869
+ };
4870
+ }
4871
+ taskFn.override = function(fixturesOrName, optionsOrFn, maybeFn) {
4872
+ const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
4873
+ getChainableContext(this).getFixtures().override(runner, userFixtures);
4874
+ return this;
4875
+ };
4876
+ taskFn.scoped = function(fixtures) {
4877
+ console.warn(`test.scoped() is deprecated and will be removed in future versions. Please use test.override() instead.`);
4878
+ return this.override(fixtures);
4330
4879
  };
4880
+ taskFn.extend = function(fixturesOrName, optionsOrFn, maybeFn) {
4881
+ const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
4882
+ const fixtures = getChainableContext(this).getFixtures().extend(runner, userFixtures);
4883
+ const _test = createTest(function(name, optionsOrFn, optionsOrTest) {
4884
+ fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
4885
+ });
4886
+ getChainableContext(_test).mergeContext({
4887
+ fixtures
4888
+ });
4889
+ return _test;
4890
+ };
4891
+ taskFn.describe = suite;
4892
+ taskFn.suite = suite;
4331
4893
  taskFn.beforeEach = beforeEach;
4332
4894
  taskFn.afterEach = afterEach;
4333
4895
  taskFn.beforeAll = beforeAll;
4334
4896
  taskFn.afterAll = afterAll;
4897
+ taskFn.aroundEach = aroundEach;
4898
+ taskFn.aroundAll = aroundAll;
4335
4899
  const _test = createChainable([
4336
4900
  "concurrent",
4337
4901
  "sequential",
@@ -4339,14 +4903,13 @@ function createTaskCollector(fn, context) {
4339
4903
  "only",
4340
4904
  "todo",
4341
4905
  "fails"
4342
- ], taskFn);
4343
- if (context) {
4344
- _test.mergeContext(context);
4345
- }
4906
+ ], taskFn, {
4907
+ fixtures: new TestFixtures()
4908
+ });
4346
4909
  return _test;
4347
4910
  }
4348
- function createTest(fn, context) {
4349
- return createTaskCollector(fn, context);
4911
+ function createTest(fn) {
4912
+ return createTaskCollector(fn);
4350
4913
  }
4351
4914
  function formatName(name) {
4352
4915
  return typeof name === "string" ? name : typeof name === "function" ? name.name || "<anonymous>" : String(name);
@@ -4380,7 +4943,7 @@ function formatTitle(template, items, idx) {
4380
4943
  const arrayElement = isArrayKey ? objectAttr(items, key) : undefined;
4381
4944
  const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement;
4382
4945
  return objDisplay(value, {
4383
- truncate: void 0
4946
+ truncate: runner?.config?.chaiConfig?.truncateThreshold
4384
4947
  });
4385
4948
  });
4386
4949
  }
@@ -4425,13 +4988,12 @@ function formatTemplateString(cases, args) {
4425
4988
  }
4426
4989
  return res;
4427
4990
  }
4428
- const now$2 = Date.now;
4991
+ const now$2 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
4429
4992
  const collectorContext = {
4430
4993
  currentSuite: null
4431
4994
  };
4432
4995
  function collectTask(task) {
4433
- var _collectorContext$cur;
4434
- (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.tasks.push(task);
4996
+ collectorContext.currentSuite?.tasks.push(task);
4435
4997
  }
4436
4998
  async function runWithSuite(suite, fn) {
4437
4999
  const prev = collectorContext.currentSuite;
@@ -4451,16 +5013,15 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
4451
5013
  runner._currentTaskStartTime = startTime;
4452
5014
  runner._currentTaskTimeout = timeout;
4453
5015
  return new Promise((resolve_, reject_)=>{
4454
- var _timer$unref;
4455
5016
  const timer = setTimeout(()=>{
4456
5017
  clearTimeout(timer);
4457
5018
  rejectTimeoutError();
4458
5019
  }, timeout);
4459
5020
  // `unref` might not exist in browser
4460
- (_timer$unref = timer.unref) === null || _timer$unref === void 0 ? void 0 : _timer$unref.call(timer);
5021
+ timer.unref?.();
4461
5022
  function rejectTimeoutError() {
4462
5023
  const error = makeTimeoutError(isHook, timeout, stackTraceError);
4463
- onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout(args, error);
5024
+ onTimeout?.(args, error);
4464
5025
  reject_(error);
4465
5026
  }
4466
5027
  function resolve(result) {
@@ -4499,6 +5060,23 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
4499
5060
  });
4500
5061
  };
4501
5062
  }
5063
+ function withCancel(fn, signal) {
5064
+ return function runWithCancel(...args) {
5065
+ return new Promise((resolve, reject)=>{
5066
+ signal.addEventListener("abort", ()=>reject(signal.reason));
5067
+ try {
5068
+ const result = fn(...args);
5069
+ if (typeof result === "object" && result != null && typeof result.then === "function") {
5070
+ result.then(resolve, reject);
5071
+ } else {
5072
+ resolve(result);
5073
+ }
5074
+ } catch (error) {
5075
+ reject(error);
5076
+ }
5077
+ });
5078
+ };
5079
+ }
4502
5080
  const abortControllers = new WeakMap();
4503
5081
  function abortIfTimeout([context], error) {
4504
5082
  if (context) {
@@ -4507,10 +5085,9 @@ function abortIfTimeout([context], error) {
4507
5085
  }
4508
5086
  function abortContextSignal(context, error) {
4509
5087
  const abortController = abortControllers.get(context);
4510
- abortController === null || abortController === void 0 ? void 0 : abortController.abort(error);
5088
+ abortController?.abort(error);
4511
5089
  }
4512
5090
  function createTestContext(test, runner) {
4513
- var _runner$extendTaskCon;
4514
5091
  const context = function() {
4515
5092
  throw new Error("done() callback is deprecated, use promise instead");
4516
5093
  };
@@ -4526,9 +5103,9 @@ function createTestContext(test, runner) {
4526
5103
  // do nothing
4527
5104
  return undefined;
4528
5105
  }
4529
- test.result ?? (test.result = {
5106
+ test.result ??= {
4530
5107
  state: "skip"
4531
- });
5108
+ };
4532
5109
  test.result.pending = true;
4533
5110
  throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
4534
5111
  };
@@ -4559,31 +5136,23 @@ function createTestContext(test, runner) {
4559
5136
  }));
4560
5137
  };
4561
5138
  context.onTestFailed = (handler, timeout)=>{
4562
- test.onFailed || (test.onFailed = []);
5139
+ test.onFailed ||= [];
4563
5140
  test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error)=>abortController.abort(error)));
4564
5141
  };
4565
5142
  context.onTestFinished = (handler, timeout)=>{
4566
- test.onFinished || (test.onFinished = []);
5143
+ test.onFinished ||= [];
4567
5144
  test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error)=>abortController.abort(error)));
4568
5145
  };
4569
- return ((_runner$extendTaskCon = runner.extendTaskContext) === null || _runner$extendTaskCon === void 0 ? void 0 : _runner$extendTaskCon.call(runner, context)) || context;
5146
+ return runner.extendTaskContext?.(context) || context;
4570
5147
  }
4571
5148
  function makeTimeoutError(isHook, timeout, stackTraceError) {
4572
5149
  const message = `${isHook ? "Hook" : "Test"} timed out in ${timeout}ms.\nIf this is a long-running ${isHook ? "hook" : "test"}, pass a timeout value as the last argument or configure it globally with "${isHook ? "hookTimeout" : "testTimeout"}".`;
4573
5150
  const error = new Error(message);
4574
- if (stackTraceError === null || stackTraceError === void 0 ? void 0 : stackTraceError.stack) {
5151
+ if (stackTraceError?.stack) {
4575
5152
  error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
4576
5153
  }
4577
5154
  return error;
4578
5155
  }
4579
- const fileContexts = new WeakMap();
4580
- function getFileContext(file) {
4581
- const context = fileContexts.get(file);
4582
- if (!context) {
4583
- throw new Error(`Cannot find file context for ${file.name}`);
4584
- }
4585
- return context;
4586
- }
4587
5156
  globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
4588
5157
  globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
4589
5158
  getSafeTimers();
@@ -4592,7 +5161,6 @@ const eventsPacks = [];
4592
5161
  const pendingTasksUpdates = [];
4593
5162
  function sendTasksUpdate(runner) {
4594
5163
  if (packs.size) {
4595
- var _runner$onTaskUpdate;
4596
5164
  const taskPacks = Array.from(packs).map(([id, task])=>{
4597
5165
  return [
4598
5166
  id,
@@ -4600,7 +5168,7 @@ function sendTasksUpdate(runner) {
4600
5168
  task[1]
4601
5169
  ];
4602
5170
  });
4603
- const p = (_runner$onTaskUpdate = runner.onTaskUpdate) === null || _runner$onTaskUpdate === void 0 ? void 0 : _runner$onTaskUpdate.call(runner, taskPacks, eventsPacks);
5171
+ const p = runner.onTaskUpdate?.(taskPacks, eventsPacks);
4604
5172
  if (p) {
4605
5173
  pendingTasksUpdates.push(p);
4606
5174
  // remove successful promise to not grow array indefnitely,
@@ -4625,12 +5193,13 @@ async function finishSendTasksUpdate(runner) {
4625
5193
  *
4626
5194
  * Vitest automatically injects the source location where the artifact was created and manages any attachments you include.
4627
5195
  *
5196
+ * **Note:** artifacts must be recorded before the task is reported. Any artifacts recorded after that will not be included in the task.
5197
+ *
4628
5198
  * @param task - The test task context, typically accessed via `this.task` in custom matchers or `context.task` in tests
4629
5199
  * @param artifact - The artifact to record. Must extend {@linkcode TestArtifactBase}
4630
5200
  *
4631
5201
  * @returns A promise that resolves to the recorded artifact with location injected
4632
5202
  *
4633
- * @throws {Error} If called after the test has finished running
4634
5203
  * @throws {Error} If the test runner doesn't support artifacts
4635
5204
  *
4636
5205
  * @example
@@ -4650,9 +5219,6 @@ async function finishSendTasksUpdate(runner) {
4650
5219
  * ```
4651
5220
  */ async function recordArtifact(task, artifact) {
4652
5221
  const runner = getRunner();
4653
- if (task.result && task.result.state !== "run") {
4654
- throw new Error(`Cannot record a test artifact outside of the test run. The test "${task.name}" finished running with the "${task.result.state}" state already.`);
4655
- }
4656
5222
  const stack = findTestFileStackTrace(task.file.filepath, new Error("STACK_TRACE").stack);
4657
5223
  if (stack) {
4658
5224
  artifact.location = {