mirror of
https://github.com/vitest-dev/vitest.git
synced 2026-02-01 17:36:51 +00:00
feat: support meta in test options (#9535)
This commit is contained in:
parent
687b633c11
commit
7d622e3d16
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
:::
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
699
test/cli/test/test-meta.test.ts
Normal file
699
test/cli/test/test-meta.test.ts
Normal 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,
|
||||
}
|
||||
`)
|
||||
})
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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, {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user