feat: support meta in test options (#9535)

This commit is contained in:
Vladimir 2026-01-29 12:24:56 +01:00 committed by GitHub
parent 687b633c11
commit 7d622e3d16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 938 additions and 16 deletions

View File

@ -143,7 +143,13 @@ test('the validation works correctly', ({ task }) => {
})
```
If the test did not finish running yet, the meta will be an empty object.
If the test did not finish running yet, the meta will be an empty object, unless it has static meta:
```ts
test('the validation works correctly', { meta: { decorated: true } })
```
Since Vitest 4.1, Vitest inherits [`meta`](/api/advanced/test-suite#meta) property defined on the [suite](/api/advanced/test-suite).
## result

View File

@ -198,24 +198,25 @@ Note that errors are serialized into simple objects: `instanceof Error` will alw
function meta(): TaskMeta
```
Custom [metadata](/api/advanced/metadata) that was attached to the suite during its execution or collection. The meta can be attached by assigning a property to the `suite.meta` object during a test run:
Custom [metadata](/api/advanced/metadata) that was attached to the suite during its execution or collection. Since Vitest 4.1, the meta can be attached by providing a `meta` object during test collection:
```ts {7,12}
```ts {7,10}
import { describe, test, TestRunner } from 'vitest'
describe('the validation works correctly', () => {
// assign "decorated" during collection
const { suite } = TestRunner.getCurrentSuite()
suite!.meta.decorated = true
describe('the validation works correctly', { meta: { decorated: true } }, () => {
test('some test', ({ task }) => {
// assign "decorated" during test run, it will be available
// only in onTestCaseReady hook
task.suite.meta.decorated = false
// tests inherit suite's metadata
task.meta.decorated === true
})
})
```
Note that suite metadata will be inherited by tests since Vitest 4.1.
:::tip
If metadata was attached during collection (outside of the `test` function), then it will be available in [`onTestModuleCollected`](./reporters#ontestmodulecollected) hook in the custom reporter.
:::

View File

@ -170,6 +170,45 @@ it('user returns data from db', { tags: ['db', 'flaky'] }, () => {
})
```
### meta <Version>4.1.0</Version> {#meta}
- **Type:** `TaskMeta`
Attaches custom [metadata](/api/advanced/metadata) available in reporters.
::: warning
Vitest merges top-level properties inherited from suites or tags. However, it does not perform a deep merge of nested objects.
```ts
import { describe, test } from 'vitest'
describe(
'nested meta',
{
meta: {
nested: { object: true, array: false },
},
},
() => {
test(
'overrides part of meta',
{
meta: {
nested: { object: false }
},
},
({ task }) => {
// task.meta === { nested: { object: false } }
// notice array got lost because "nested" object was overriden
}
)
}
)
```
Prefer using non-nested meta, if possible.
:::
### concurrent
- **Type:** `boolean`

View File

@ -331,16 +331,32 @@ function createSuiteCollector(
// higher priority should be last, run 1, 2, 3, ... etc
.sort((tag1, tag2) => (tag2.priority ?? POSITIVE_INFINITY) - (tag1.priority ?? POSITIVE_INFINITY))
.reduce((acc, tag) => {
const { name, description, priority, ...options } = tag
const { name, description, priority, meta, ...options } = tag
Object.assign(acc, options)
if (meta) {
acc.meta = Object.assign(acc.meta ?? Object.create(null), meta)
}
return acc
}, {} as TestOptions)
const testOwnMeta = options.meta
options = {
...tagsOptions,
...options,
}
const timeout = options.timeout ?? runner.config.testTimeout
const parentMeta = currentSuite?.meta
const tagMeta = tagsOptions.meta
const testMeta = Object.create(null)
if (tagMeta) {
Object.assign(testMeta, tagMeta)
}
if (parentMeta) {
Object.assign(testMeta, parentMeta)
}
if (testOwnMeta) {
Object.assign(testMeta, testOwnMeta)
}
const task: Test = {
id: '',
name,
@ -365,7 +381,7 @@ function createSuiteCollector(
: options.todo
? 'todo'
: 'run',
meta: options.meta ?? Object.create(null),
meta: testMeta,
annotations: [],
artifacts: [],
tags: testTags,
@ -513,7 +529,7 @@ function createSuiteCollector(
file: (currentSuite?.file ?? collectorContext.currentSuite?.file)!,
shuffle: suiteOptions?.shuffle,
tasks: [],
meta: Object.create(null),
meta: suiteOptions?.meta ?? Object.create(null),
concurrent: suiteOptions?.concurrent,
tags: unique([...parentTask?.tags || [], ...suiteTags]),
}
@ -604,9 +620,10 @@ function createSuite() {
const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false
const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false
const { meta: parentMeta, ...parentOptions } = currentSuite?.options || {}
// inherit options from current suite
options = {
...currentSuite?.options,
...parentOptions,
...options,
}
@ -638,6 +655,10 @@ function createSuite() {
options.sequential = isSequential && !isConcurrent
}
if (parentMeta) {
options.meta = Object.assign(Object.create(null), parentMeta, options.meta)
}
return createSuiteCollector(
formatName(name),
factory,

View File

@ -570,6 +570,10 @@ export interface TestOptions {
tags?: keyof TestTags extends never
? string[] | string
: TestTags[keyof TestTags] | TestTags[keyof TestTags][]
/**
* Custom test metadata available to reporters.
*/
meta?: Partial<TaskMeta>
}
export interface TestTags {}
@ -735,10 +739,6 @@ export interface TaskCustomOptions extends TestOptions {
* Whether the task was produced with `.each()` method.
*/
each?: boolean
/**
* Custom metadata for the task that will be assigned to `task.meta`.
*/
meta?: Record<string, unknown>
/**
* Task fixtures.
*/

View File

@ -0,0 +1,699 @@
import type { TestCase, TestSuite } from 'vitest/node'
import { runInlineTests } from '#test-utils'
import { expect, test } from 'vitest'
test('meta can be defined on test options', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
test('test 1', { meta: { custom: 'value', count: 42 } }, () => {})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testCase = testModule.children.at(0) as TestCase
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"count": 42,
"custom": "value",
}
`)
})
test('meta can be defined on suite options', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: { suiteKey: 'suiteValue' } }, () => {
test('test 1', () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
expect(testSuite.meta()).toMatchInlineSnapshot(`
{
"suiteKey": "suiteValue",
}
`)
})
test('test inherits meta from parent suite', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: { inherited: true, level: 'suite' } }, () => {
test('test 1', () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
const testCase = testSuite.children.at(0) as TestCase
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"inherited": true,
"level": "suite",
}
`)
})
test('test meta overrides inherited suite meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: { shared: 'fromSuite', suiteOnly: true } }, () => {
test('test 1', { meta: { shared: 'fromTest', testOnly: 123 } }, () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
const testCase = testSuite.children.at(0) as TestCase
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"shared": "fromTest",
"suiteOnly": true,
"testOnly": 123,
}
`)
})
test('nested suites inherit meta from parent suites', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('outer', { meta: { outer: true } }, () => {
describe('inner', { meta: { inner: true } }, () => {
test('test 1', () => {})
})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const outerSuite = testModule.children.at(0) as TestSuite
const innerSuite = outerSuite.children.at(0) as TestSuite
const testCase = innerSuite.children.at(0) as TestCase
expect(outerSuite.meta()).toMatchInlineSnapshot(`
{
"outer": true,
}
`)
expect(innerSuite.meta()).toMatchInlineSnapshot(`
{
"inner": true,
"outer": true,
}
`)
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"inner": true,
"outer": true,
}
`)
})
test('deeply nested meta inheritance with overrides', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('level1', { meta: { level: 1, a: 'first' } }, () => {
describe('level2', { meta: { level: 2, b: 'second' } }, () => {
describe('level3', { meta: { level: 3, a: 'override' } }, () => {
test('test 1', { meta: { level: 4 } }, () => {})
})
})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const level1 = testModule.children.at(0) as TestSuite
const level2 = level1.children.at(0) as TestSuite
const level3 = level2.children.at(0) as TestSuite
const testCase = level3.children.at(0) as TestCase
expect(level1.meta()).toMatchInlineSnapshot(`
{
"a": "first",
"level": 1,
}
`)
expect(level2.meta()).toMatchInlineSnapshot(`
{
"a": "first",
"b": "second",
"level": 2,
}
`)
expect(level3.meta()).toMatchInlineSnapshot(`
{
"a": "override",
"b": "second",
"level": 3,
}
`)
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"a": "override",
"b": "second",
"level": 4,
}
`)
})
test('meta is accessible from task.meta inside tests', async () => {
const { stderr, stdout } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: { suiteKey: 'inherited' } }, () => {
test('test 1', { meta: { testKey: 'own' } }, ({ task }) => {
console.log('META:', JSON.stringify(task.meta))
})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const metaLine = stdout.split('\n').find(line => line.startsWith('META:'))
expect(metaLine).toBeDefined()
expect(JSON.parse(metaLine!.slice('META:'.length))).toMatchInlineSnapshot(`
{
"suiteKey": "inherited",
"testKey": "own",
}
`)
})
test('sibling tests have independent meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: { shared: 'parent' } }, () => {
test('test 1', { meta: { id: 1 } }, () => {})
test('test 2', { meta: { id: 2 } }, () => {})
test('test 3', () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
const [test1, test2, test3] = testSuite.children.array() as TestCase[]
expect(test1.meta()).toMatchInlineSnapshot(`
{
"id": 1,
"shared": "parent",
}
`)
expect(test2.meta()).toMatchInlineSnapshot(`
{
"id": 2,
"shared": "parent",
}
`)
expect(test3.meta()).toMatchInlineSnapshot(`
{
"shared": "parent",
}
`)
})
test('sibling suites have independent meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite1', { meta: { suite: 1 } }, () => {
test('test 1', () => {})
})
describe('suite2', { meta: { suite: 2 } }, () => {
test('test 2', () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const [suite1, suite2] = testModule.children.array() as TestSuite[]
const test1 = suite1.children.at(0) as TestCase
const test2 = suite2.children.at(0) as TestCase
expect(suite1.meta()).toMatchInlineSnapshot(`
{
"suite": 1,
}
`)
expect(suite2.meta()).toMatchInlineSnapshot(`
{
"suite": 2,
}
`)
expect(test1.meta()).toMatchInlineSnapshot(`
{
"suite": 1,
}
`)
expect(test2.meta()).toMatchInlineSnapshot(`
{
"suite": 2,
}
`)
})
test('test without parent suite has empty meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
test('test 1', () => {})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testCase = testModule.children.at(0) as TestCase
expect(testCase.meta()).toMatchInlineSnapshot(`{}`)
})
test('test.each works with meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: { feature: 'each' } }, () => {
test.each([1, 2, 3])('test %i', { meta: { eachTest: true } }, () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
const tests = testSuite.children.array() as TestCase[]
expect(tests).toHaveLength(3)
for (const test of tests) {
expect(test.meta()).toMatchInlineSnapshot(`
{
"eachTest": true,
"feature": "each",
}
`)
}
})
test('describe.each works with meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe.each([1, 2])('suite %i', { meta: { dynamic: true } }, () => {
test('test', () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const [suite1, suite2] = testModule.children.array() as TestSuite[]
expect(suite1.meta()).toMatchInlineSnapshot(`
{
"dynamic": true,
}
`)
expect(suite2.meta()).toMatchInlineSnapshot(`
{
"dynamic": true,
}
`)
expect((suite1.children.at(0) as TestCase).meta()).toMatchInlineSnapshot(`
{
"dynamic": true,
}
`)
})
test('concurrent tests have independent meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: { shared: true } }, () => {
test.concurrent('test 1', { meta: { id: 1 } }, () => {})
test.concurrent('test 2', { meta: { id: 2 } }, () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
const [test1, test2] = testSuite.children.array() as TestCase[]
expect(test1.meta()).toMatchInlineSnapshot(`
{
"id": 1,
"shared": true,
}
`)
expect(test2.meta()).toMatchInlineSnapshot(`
{
"id": 2,
"shared": true,
}
`)
})
test('meta with complex values', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
test('test 1', {
meta: {
nested: { a: { b: { c: 1 } } },
array: [1, 2, 3],
nullValue: null,
boolTrue: true,
boolFalse: false,
num: 42.5,
}
}, () => {})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testCase = testModule.children.at(0) as TestCase
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"array": [
1,
2,
3,
],
"boolFalse": false,
"boolTrue": true,
"nested": {
"a": {
"b": {
"c": 1,
},
},
},
"nullValue": null,
"num": 42.5,
}
`)
})
test('meta works with test modifiers (skip, only, todo)', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
test.skip('skipped test', { meta: { status: 'skipped' } }, () => {})
test.todo('todo test', { meta: { status: 'todo' } })
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const [skipped, todo] = testModule.children.array() as TestCase[]
expect(skipped.meta()).toMatchInlineSnapshot(`
{
"status": "skipped",
}
`)
expect(todo.meta()).toMatchInlineSnapshot(`
{
"status": "todo",
}
`)
})
test('meta works with test.fails', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
test.fails('failing test', { meta: { expectFailure: true } }, () => {
throw new Error('Expected error')
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testCase = testModule.children.at(0) as TestCase
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"expectFailure": true,
}
`)
expect(testCase.result().state).toBe('passed')
})
test('suite without meta does not inherit to tests', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite without meta', () => {
test('test with meta', { meta: { ownMeta: true } }, () => {})
test('test without meta', () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
const [withMeta, withoutMeta] = testSuite.children.array() as TestCase[]
expect(testSuite.meta()).toMatchInlineSnapshot(`{}`)
expect(withMeta.meta()).toMatchInlineSnapshot(`
{
"ownMeta": true,
}
`)
expect(withoutMeta.meta()).toMatchInlineSnapshot(`{}`)
})
test('meta does not mutate parent when child overrides', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('parent', { meta: { key: 'parent', parentOnly: true } }, () => {
describe('child', { meta: { key: 'child', childOnly: true } }, () => {
test('test', () => {})
})
test('sibling test', () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const parent = testModule.children.at(0) as TestSuite
const child = parent.children.at(0) as TestSuite
const siblingTest = parent.children.at(1) as TestCase
expect(parent.meta()).toMatchInlineSnapshot(`
{
"key": "parent",
"parentOnly": true,
}
`)
expect(child.meta()).toMatchInlineSnapshot(`
{
"childOnly": true,
"key": "child",
"parentOnly": true,
}
`)
expect(siblingTest.meta()).toMatchInlineSnapshot(`
{
"key": "parent",
"parentOnly": true,
}
`)
})
test('meta with test.for', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: { fromSuite: true } }, () => {
test.for([
{ input: 1, expected: 2 },
{ input: 2, expected: 4 },
])('test $input', { meta: { forTest: true } }, ({ input, expected }) => {
expect(input * 2).toBe(expected)
})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
const tests = testSuite.children.array() as TestCase[]
expect(tests).toHaveLength(2)
for (const test of tests) {
expect(test.meta()).toMatchInlineSnapshot(`
{
"forTest": true,
"fromSuite": true,
}
`)
}
})
test('empty meta object is allowed', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { meta: {} }, () => {
test('test', { meta: {} }, () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testSuite = testModule.children.at(0) as TestSuite
const testCase = testSuite.children.at(0) as TestCase
expect(testSuite.meta()).toMatchInlineSnapshot(`{}`)
expect(testCase.meta()).toMatchInlineSnapshot(`{}`)
})
test('meta inheritance across multiple files', async () => {
const { stderr, ctx } = await runInlineTests({
'file1.test.js': `
describe('suite in file1', { meta: { file: 1 } }, () => {
test('test 1', () => {})
})
`,
'file2.test.js': `
describe('suite in file2', { meta: { file: 2 } }, () => {
test('test 2', () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
},
},
})
expect(stderr).toBe('')
const testModules = ctx!.state.getTestModules()
const file1Module = testModules.find(m => m.moduleId.includes('file1'))!
const file2Module = testModules.find(m => m.moduleId.includes('file2'))!
const suite1 = file1Module.children.at(0) as TestSuite
const suite2 = file2Module.children.at(0) as TestSuite
const test1 = suite1.children.at(0) as TestCase
const test2 = suite2.children.at(0) as TestCase
expect(test1.meta()).toMatchInlineSnapshot(`
{
"file": 1,
}
`)
expect(test2.meta()).toMatchInlineSnapshot(`
{
"file": 2,
}
`)
})

View File

@ -1255,6 +1255,139 @@ test('multiple filter expressions act as AND', async () => {
`)
})
test('tags can define meta in config', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
test('test 1', { tags: ['unit'] }, () => {})
test('test 2', { tags: ['e2e'] }, () => {})
test('test 3', { tags: ['unit', 'slow'] }, () => {})
`,
'vitest.config.js': {
test: {
globals: true,
tags: [
{ name: 'unit', meta: { type: 'unit', priority: 1 } },
{ name: 'e2e', meta: { type: 'e2e', browser: true } },
{ name: 'slow', meta: { priority: 2, slow: true } },
],
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const [test1, test2, test3] = testModule.children.array() as TestCase[]
expect(test1.meta()).toMatchInlineSnapshot(`
{
"priority": 1,
"type": "unit",
}
`)
expect(test2.meta()).toMatchInlineSnapshot(`
{
"browser": true,
"type": "e2e",
}
`)
expect(test3.meta()).toMatchInlineSnapshot(`
{
"priority": 2,
"slow": true,
"type": "unit",
}
`)
})
test('tag meta is inherited by suite and test meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
describe('suite', { tags: ['suite-tag'], meta: { suiteOwn: true } }, () => {
test('test', { tags: ['test-tag'], meta: { testOwn: true } }, () => {})
})
`,
'vitest.config.js': {
test: {
globals: true,
tags: [
{ name: 'suite-tag', meta: { fromSuiteTag: 'value' } },
{ name: 'test-tag', meta: { fromTestTag: 'value' } },
],
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const suite = testModule.children.at(0) as TestSuite
const testCase = suite.children.at(0) as TestCase
// suite has a tag with metadata, but tags are only applied to tests,
// so suites don't get tag metadata
expect(suite.meta()).toMatchInlineSnapshot(`
{
"suiteOwn": true,
}
`)
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"fromSuiteTag": "value",
"fromTestTag": "value",
"suiteOwn": true,
"testOwn": true,
}
`)
})
test('test meta overrides tag meta', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
test('test', { tags: ['tagged'], meta: { key: 'fromTest', testOnly: true } }, () => {})
`,
'vitest.config.js': {
test: {
globals: true,
tags: [
{ name: 'tagged', meta: { key: 'fromTag', tagOnly: true } },
],
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testCase = testModule.children.at(0) as TestCase
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"key": "fromTest",
"tagOnly": true,
"testOnly": true,
}
`)
})
test('multiple tags with meta are merged with priority order', async () => {
const { stderr, ctx } = await runInlineTests({
'basic.test.js': `
test('test', { tags: ['low', 'high'] }, () => {})
`,
'vitest.config.js': {
test: {
globals: true,
tags: [
{ name: 'low', priority: 2, meta: { shared: 'low', lowOnly: true } },
{ name: 'high', priority: 1, meta: { shared: 'high', highOnly: true } },
],
},
},
})
expect(stderr).toBe('')
const testModule = ctx!.state.getTestModules()[0]
const testCase = testModule.children.at(0) as TestCase
expect(testCase.meta()).toMatchInlineSnapshot(`
{
"highOnly": true,
"lowOnly": true,
"shared": "high",
}
`)
})
function getTestTree(builder: (fn: (test: TestCase) => any) => any) {
return builder(test => test.options.tags)
}
@ -1272,3 +1405,20 @@ function removeUndefined<T extends Record<string, any>>(obj: T): Partial<T> {
}
return result
}
declare module 'vitest' {
interface TaskMeta {
type?: string
priority?: number
browser?: boolean
slow?: boolean
fromSuiteTag?: string
fromTestTag?: string
suiteOwn?: boolean
testOwn?: boolean
tagOnly?: boolean
shared?: string
lowOnly?: boolean
highOnly?: boolean
}
}

View File

@ -10,6 +10,12 @@ import {
} from 'vitest'
import { Gardener } from '../src/custom/gardener.js'
declare module 'vitest' {
interface TaskMeta {
customPropertyToDifferentiateTask?: boolean
}
}
// this function will be called, when Vitest collects tasks
const myCustomTask = TestRunner.createChainable(['todo'], function (name: string, fn: () => void) {
TestRunner.getCurrentSuite().task(name, {