-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: Add meta to TestOptions
#8405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -190,6 +190,28 @@ function assert(condition: any, message: string) { | |
| } | ||
| } | ||
|
|
||
| function collectAncestorMeta(suite: Suite | undefined): Record<string, unknown> { | ||
| const ancestorMeta = Object.create(null) | ||
| let current = suite | ||
|
|
||
| // Walk up the suite hierarchy and collect metadata | ||
| // We'll collect from root to child so that closer ancestors override distant ones | ||
| const suites: Suite[] = [] | ||
| while (current) { | ||
| if (current.meta) { | ||
| suites.unshift(current) // Add to beginning so we process from root to child | ||
| } | ||
| current = current.suite | ||
| } | ||
|
|
||
| // Merge metadata with closer ancestors having higher priority | ||
| for (const s of suites) { | ||
| Object.assign(ancestorMeta, s.meta) | ||
| } | ||
|
|
||
| return ancestorMeta | ||
| } | ||
|
|
||
| export function getDefaultSuite(): SuiteCollector<object> { | ||
| assert(defaultSuite, 'the default suite') | ||
| return defaultSuite | ||
|
|
@@ -325,7 +347,10 @@ function createSuiteCollector( | |
| : options.todo | ||
| ? 'todo' | ||
| : 'run', | ||
| meta: options.meta ?? Object.create(null), | ||
| meta: { | ||
| ...collectAncestorMeta(collectorContext.currentSuite?.suite), | ||
| ...(options.meta || {}), | ||
| }, | ||
| annotations: [], | ||
| } | ||
| const handler = options.handler | ||
|
|
@@ -457,7 +482,9 @@ function createSuiteCollector( | |
| file: undefined!, | ||
| shuffle: suiteOptions?.shuffle, | ||
| tasks: [], | ||
| meta: Object.create(null), | ||
| meta: { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
| ...(suiteOptions?.meta || {}), | ||
| }, | ||
| concurrent: suiteOptions?.concurrent, | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -485,6 +485,10 @@ export interface TestOptions { | |
| * Whether the test is expected to fail. If it does, the test will pass, otherwise it will fail. | ||
| */ | ||
| fails?: boolean | ||
| /** | ||
| * Custom metadata for the task. This will be merged with any meta property defined in the test. | ||
| */ | ||
| meta?: Partial<TaskMeta> | ||
| } | ||
|
|
||
| interface ExtendedAPI<ExtraContext> { | ||
|
|
@@ -637,7 +641,7 @@ export interface TaskCustomOptions extends TestOptions { | |
| /** | ||
| * Custom metadata for the task that will be assigned to `task.meta`. | ||
| */ | ||
| meta?: Record<string, unknown> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sheremet-va This is what I meant here |
||
| meta?: Partial<TaskMeta> | ||
| /** | ||
| * Task fixtures. | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| import { beforeEach, describe, expect, test } from 'vitest' | ||
|
|
||
| describe('TestOptions meta property functionality', { meta: { suiteLevel: 'test-suite', priority: 'medium' } }, () => { | ||
| let beforeEachMeta: Record<string, unknown> | ||
|
|
||
| beforeEach(({ task }) => { | ||
| beforeEachMeta = { ...task.meta } | ||
| }) | ||
|
|
||
| test('should merge suite and test meta properties', { meta: { testLevel: 'individual-test', priority: 'high' } }, ({ task }) => { | ||
|
Check failure on line 10 in test/core/test/test-meta-options.test.ts
|
||
| // Test should have both suite and test meta | ||
| expect(task.meta).toMatchObject({ | ||
| suiteLevel: 'test-suite', | ||
| testLevel: 'individual-test', | ||
| priority: 'high', // test meta should override suite meta | ||
| }) | ||
|
|
||
| // beforeEach should have access to merged meta | ||
| expect(beforeEachMeta).toMatchObject({ | ||
| suiteLevel: 'test-suite', | ||
| testLevel: 'individual-test', | ||
| priority: 'high', | ||
| }) | ||
| }) | ||
|
|
||
| test('should inherit suite meta when no test meta provided', ({ task }) => { | ||
| // Test should only have suite meta | ||
| expect(task.meta).toMatchObject({ | ||
| suiteLevel: 'test-suite', | ||
| priority: 'medium', | ||
| }) | ||
|
|
||
| // beforeEach should have access to suite meta | ||
| expect(beforeEachMeta).toMatchObject({ | ||
| suiteLevel: 'test-suite', | ||
| priority: 'medium', | ||
| }) | ||
| }) | ||
|
|
||
| test('should allow adding meta at runtime', { meta: { testLevel: 'runtime-test' } }, ({ task }) => { | ||
|
Check failure on line 40 in test/core/test/test-meta-options.test.ts
|
||
| // Add meta at runtime | ||
| task.meta.runtimeAdded = 'added-during-test' | ||
|
|
||
| expect(task.meta).toMatchObject({ | ||
| suiteLevel: 'test-suite', | ||
| testLevel: 'runtime-test', | ||
| priority: 'medium', | ||
| runtimeAdded: 'added-during-test', | ||
| }) | ||
| }) | ||
|
|
||
| test('should differentiate between task.meta and task.suite.meta', { meta: { testLevel: 'child-test', priority: 'high' } }, ({ task }) => { | ||
|
Check failure on line 52 in test/core/test/test-meta-options.test.ts
|
||
| // task.meta should contain merged metadata (suite + test) | ||
| expect(task.meta).toMatchObject({ | ||
| suiteLevel: 'test-suite', | ||
| testLevel: 'child-test', | ||
| priority: 'high', // test overrides suite | ||
| }) | ||
|
|
||
| // task.suite.meta should contain only suite's own metadata | ||
| expect(task.suite?.meta).toMatchObject({ | ||
| suiteLevel: 'test-suite', | ||
| priority: 'medium', // original suite priority | ||
| }) | ||
|
|
||
| // They should be different objects | ||
| expect(task.meta).not.toBe(task.suite?.meta) | ||
|
|
||
| // task.suite.meta should NOT have test-specific metadata | ||
| expect(task.suite?.meta).not.toHaveProperty('testLevel') | ||
| }) | ||
| }) | ||
|
|
||
| describe('Suite without meta', () => { | ||
| let beforeEachMeta: Record<string, unknown> | ||
|
|
||
| beforeEach(({ task }) => { | ||
| beforeEachMeta = { ...task.meta } | ||
| }) | ||
|
|
||
| test('should only have test meta when suite has no meta', { meta: { testOnly: 'test-meta' } }, ({ task }) => { | ||
|
Check failure on line 81 in test/core/test/test-meta-options.test.ts
|
||
| expect(task.meta).toMatchObject({ | ||
| testOnly: 'test-meta', | ||
| }) | ||
|
|
||
| expect(beforeEachMeta).toMatchObject({ | ||
| testOnly: 'test-meta', | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| describe('Nested describes metadata cascading', { meta: { grandparent: 'top-level', priority: 'low' } }, () => { | ||
| describe('Middle suite', { meta: { parent: 'middle-level', priority: 'medium' } }, () => { | ||
| test('should cascade metadata from all ancestor suites', ({ task }) => { | ||
| // Should now get metadata from all ancestors: grandparent + parent | ||
| expect(task.meta).toMatchObject({ | ||
| grandparent: 'top-level', // from grandparent suite | ||
| parent: 'middle-level', // from parent suite | ||
| priority: 'medium', // parent overrides grandparent | ||
| }) | ||
|
|
||
| // Original suite metadata should be preserved | ||
| expect(task.suite?.meta).toMatchObject({ | ||
| parent: 'middle-level', | ||
| priority: 'medium', | ||
| }) | ||
|
|
||
| // Grandparent suite metadata should also be preserved | ||
| expect(task.suite?.suite?.meta).toMatchObject({ | ||
| grandparent: 'top-level', | ||
| priority: 'low', | ||
| }) | ||
| }) | ||
|
|
||
| test('test metadata should override cascaded suite metadata', { meta: { testLevel: 'child', priority: 'highest' } }, ({ task }) => { | ||
| // Should get metadata from all ancestors plus test metadata | ||
| expect(task.meta).toMatchObject({ | ||
| grandparent: 'top-level', // from grandparent suite | ||
| parent: 'middle-level', // from parent suite | ||
| testLevel: 'child', // from test | ||
| priority: 'highest', // test overrides all ancestors | ||
| }) | ||
| }) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep the meta with a null prototype. Just assign
options.metato meta collected withcollectAncestorMeta: