diff --git a/packages/runner/src/context.ts b/packages/runner/src/context.ts index f793b03c6..00dea0a9b 100644 --- a/packages/runner/src/context.ts +++ b/packages/runner/src/context.ts @@ -136,7 +136,7 @@ export function withCancel any>( const abortControllers = new WeakMap() -export function abortIfTimeout([context]: [TestContext?], error: Error): void { +export function abortIfTimeout([context]: [TestContext?, unknown?], error: Error): void { if (context) { abortContextSignal(context, error) } diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index 470a15145..002bb7bc6 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -291,13 +291,18 @@ export interface WithFixturesOptions { * Current fixtures from the context. */ fixtures?: TestFixtures + /** + * The suite to use for fixture lookups. + * Used by beforeEach/afterEach/aroundEach hooks to pick up fixture overrides from the test's describe block. + */ + suite?: Suite } const contextHasFixturesCache = new WeakMap>() export function withFixtures(fn: Function, options?: WithFixturesOptions) { const collector = getCurrentSuite() - const suite = collector.suite || collector.file + const suite = options?.suite || collector.suite || collector.file return async (hookContext?: TestContext): Promise => { const context: (TestContext & { [key: string]: any }) | undefined = hookContext || options?.context as TestContext diff --git a/packages/runner/src/hooks.ts b/packages/runner/src/hooks.ts index 9e3d122b3..a8b800cb6 100644 --- a/packages/runner/src/hooks.ts +++ b/packages/runner/src/hooks.ts @@ -157,11 +157,17 @@ export function beforeEach( ): void { assertTypes(fn, '"beforeEach" callback', ['function']) const stackTraceError = new Error('STACK_TRACE_ERROR') + + const wrapper: BeforeEachListener = (context, suite) => { + const fixtureResolver = withFixtures(fn, { suite }) + return fixtureResolver(context) + } + return getCurrentSuite().on( 'beforeEach', Object.assign( withTimeout( - withFixtures(fn), + wrapper, timeout ?? getDefaultHookTimeout(), true, stackTraceError, @@ -197,10 +203,15 @@ export function afterEach( timeout?: number, ): void { assertTypes(fn, '"afterEach" callback', ['function']) + const wrapper: AfterEachListener = (context, suite) => { + const fixtureResolver = withFixtures(fn, { suite }) + return fixtureResolver(context) + } + return getCurrentSuite().on( 'afterEach', withTimeout( - withFixtures(fn), + wrapper, timeout ?? getDefaultHookTimeout(), true, new Error('STACK_TRACE_ERROR'), @@ -375,7 +386,7 @@ export function aroundEach( const innerFn = (ctx: any) => fn(runTest, ctx, suite) configureProps(innerFn, { index: 1, original: fn }) - const fixtureResolver = withFixtures(innerFn) + const fixtureResolver = withFixtures(innerFn, { suite }) return fixtureResolver(context) } diff --git a/test/core/test/test-extend.test.ts b/test/core/test/test-extend.test.ts index 9ef887382..4cdc453a7 100644 --- a/test/core/test/test-extend.test.ts +++ b/test/core/test/test-extend.test.ts @@ -640,3 +640,136 @@ describe('builder pattern with non-function values', () => { expect(chainedSync).toBe('HELLO WORLD') }) }) + +// https://github.com/vitest-dev/vitest/issues/9810 +describe('override auto fixture with outer beforeEach', () => { + const myTest = test + .extend('base', { auto: true }, () => 'base:default') + .extend('derived', { auto: true }, ({ base }) => { + return `derived:${base}` + }) + + beforeEach(({ task }) => { + expect(task).toBeTruthy() + }) + + describe('with override', () => { + myTest.override('base', 'base:override') + + myTest('auto fixture sees overridden dependency', ({ base, derived }) => { + expect(base).toBe('base:override') + expect(derived).toBe('derived:base:override') + }) + }) +}) + +describe('override auto fixture with co-located beforeEach', () => { + const myTest = test + .extend('base', { auto: true }, () => 'base:default') + .extend('derived', { auto: true }, ({ base }) => { + return `derived:${base}` + }) + + myTest.override('base', 'base:override') + + beforeEach(({ task }) => { + expect(task).toBeTruthy() + }) + + myTest('override applies when beforeEach is co-located', ({ base, derived }) => { + expect(base).toBe('base:override') + expect(derived).toBe('derived:base:override') + }) +}) + +describe('override non-auto fixture with outer beforeEach', () => { + const myTest = test + .extend('base', () => 'base:default') + .extend('derived', ({ base }) => `derived:${base}`) + + beforeEach(({ task }) => { + expect(task).toBeTruthy() + }) + + describe('with override', () => { + myTest.override('base', 'base:override') + + myTest('override applies to non-auto dependency', ({ base, derived }) => { + expect(base).toBe('base:override') + expect(derived).toBe('derived:base:override') + }) + }) +}) + +describe('override fixture accessed in outer beforeEach', () => { + const myTest = test + .extend('base', () => 'base:default') + .extend('derived', ({ base }) => `derived:${base}`) + + const hookValues: string[] = [] + + myTest.beforeEach(({ base }) => { + hookValues.push(base) + }) + + describe('with override', () => { + myTest.override('base', 'base:override') + + myTest('beforeEach sees overridden fixture', ({ derived }) => { + expect(hookValues).toEqual(['base:override']) + expect(derived).toBe('derived:base:override') + }) + }) +}) + +describe('nested overrides with outer beforeEach', () => { + const myTest = test + .extend('base', { auto: true }, () => 'base:default') + .extend('derived', { auto: true }, ({ base }) => { + return `derived:${base}` + }) + + beforeEach(({ task }) => { + expect(task).toBeTruthy() + }) + + describe('outer', () => { + myTest.override('base', 'base:outer') + + myTest('outer override', ({ base, derived }) => { + expect(base).toBe('base:outer') + expect(derived).toBe('derived:base:outer') + }) + + describe('inner', () => { + myTest.override('base', 'base:inner') + + myTest('inner override wins', ({ base, derived }) => { + expect(base).toBe('base:inner') + expect(derived).toBe('derived:base:inner') + }) + }) + }) +}) + +describe('override fixture accessed in aroundEach', () => { + const myTest = test + .extend('base', () => 'base:default') + .extend('derived', ({ base }) => `derived:${base}`) + + const hookValues: string[] = [] + + myTest.aroundEach(async (runTest, { base }) => { + hookValues.push(base) + await runTest() + }) + + describe('with override', () => { + myTest.override('base', 'base:override') + + myTest('aroundEach sees overridden fixture', ({ derived }) => { + expect(hookValues).toEqual(['base:override']) + expect(derived).toBe('derived:base:override') + }) + }) +})