Skip to content

Conversation

@sheremet-va
Copy link
Member

@sheremet-va sheremet-va commented Jan 29, 2026

Description

TODO

  • Fix errors in rolldown

This PR adds new test.extend syntax that supports type inference:

import { test as baseTest } from 'vitest'

const test = baseTest
  .extend('config', { scope: 'worker' }, async () => {
    return { port: 3000, host: 'localhost' }
  })
  .extend('database', { scope: 'file' }, async ({ config }, { onCleanup }) => {
    const db = new Database(`${config.host}:${config.port}`)
    await db.connect()

    onCleanup(async () => {
      await db.disconnect()
    })

    return db
  })
  .extend('user', async ({ database }) => {
    return await database.createTestUser()
  })

test('user is created in database', async ({ user, database }) => {
  // TypeScript knows:
  // - user is the return type of createTestUser()
  // - database is Database
  const found = await database.findUser(user.id)
  expect(found).toEqual(user)
})

Fixes #9305
Fixes #9555

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. If the feature is substantial or introduces breaking changes without a discussion, PR might be closed.
  • Ideally, include a test that fails without this PR but passes with it.
  • Please, don't make changes to pnpm-lock.yaml unless you introduce a new test example.
  • Please check Allow edits by maintainers to make review process faster. Note that this option is not available for repositories that are owned by Github organizations.

Tests

  • Run the tests with pnpm test:ci.

Documentation

  • If you introduce new functionality, document it. You can run documentation with pnpm run docs command.

Changesets

  • Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with feat:, fix:, perf:, docs:, or chore:.

Copy link
Member Author

sheremet-va commented Jan 29, 2026

@netlify
Copy link

netlify bot commented Jan 29, 2026

Deploy Preview for vitest-dev ready!

Name Link
🔨 Latest commit 5f3c003
🔍 Latest deploy log https://app.netlify.com/projects/vitest-dev/deploys/697dcd929b72c60008b65d32
😎 Deploy Preview https://deploy-preview-9550--vitest-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@sheremet-va sheremet-va force-pushed the 01-29-feat_support_type_inference_with_new_test.extend_syntax branch 3 times, most recently from 4f3ae6f to af1c234 Compare January 29, 2026 18:02
@sheremet-va sheremet-va requested a review from hi-ogawa January 29, 2026 18:02
@sheremet-va sheremet-va marked this pull request as ready for review January 29, 2026 18:03
@sheremet-va sheremet-va added this to the 4.1.0 milestone Jan 29, 2026
@sheremet-va sheremet-va force-pushed the 01-29-feat_support_type_inference_with_new_test.extend_syntax branch 3 times, most recently from b2c0ea8 to cf27bce Compare January 29, 2026 18:14
@sheremet-va sheremet-va changed the title feat: support type inference with new test.extend syntax feat: support type inference with a new test.extend syntax Jan 29, 2026
Copy link
Contributor

@hi-ogawa hi-ogawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API looks better to me than use magic. It still delegates to use mechanism internally, but I like it.
I haven't look what's going to happen in next PR #9553

@sheremet-va sheremet-va force-pushed the 01-29-feat_support_type_inference_with_new_test.extend_syntax branch 3 times, most recently from 45fe275 to c3a889c Compare January 30, 2026 19:10
}, { globals: true })
expect(stdout).toContain('basic.test.ts')
expect(stderr).toBe('')
expect(stderr).toMatchInlineSnapshot(`
Copy link
Member Author

@sheremet-va sheremet-va Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change, but it simplifies mental model A LOT. Scoped fixtures can only use other scoped fixtures with a higher scope, and scope is explicit (but overriding the fixture also inherits all the options by default, like in PW).

By default, any extended value has a test scope. You cannot override the extended value to provide a DIFFERENT scope, it now throws an error. This is needed to remove the ambiguity of defining a worker fixture and then overriding its "static" dependency inside the suite:

// this worked previously
const test = base
  .extend('port', 5300)
  .extend('config', { scope: 'worker' }, ({ port }) => {
    return `localhost:${port}`
  })

But if you want to override the port, you need to invalidate or overwrite the WORKER fixture that we already initialized - which makes little sense

test.override('port', 5000) // what to do here?

Using test.override with a scope other than test in general is now forbidden, unless it's called at the top level of the file (which still causes some unexpected issues with worker fixtures because they will leak to other test files in the same worker - I wonder if we could force a new worker instead).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also makes it so you cannot call test.extend('', { scope: 'file' }) inside a suite, it's only allowed in the top level of the module now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't checking these test cases previously and the new extend/override behavior makes sense to me.

@sheremet-va
Copy link
Member Author

sheremet-va commented Jan 30, 2026

@hi-ogawa I updated the fixtures inheritance (there is now a TestFixters class that tracks test's fixtures), please take a look. I am also introducing a small breaking change, not sure if we can put it into 4.1, but the impact should be small, I believe (I could probably implement it with backwards compatibility if we are ok with small bugs)

@sheremet-va sheremet-va force-pushed the 01-29-feat_support_type_inference_with_new_test.extend_syntax branch from c3a889c to 225e631 Compare January 30, 2026 19:33
>> db fixture teardown
>> aroundEach teardown"
>> aroundEach teardown
>> db fixture teardown"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behaviour seems to make more sense than what was here before because you should be able to access db after calling runTest(). Previously the db was torn down by that point 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this looks like what I suggested to happen in #9450 (comment) I forgot to double check the test.

@sheremet-va sheremet-va force-pushed the 01-29-feat_support_type_inference_with_new_test.extend_syntax branch 2 times, most recently from 8dc09a8 to 3d73ede Compare January 30, 2026 21:01
@sheremet-va sheremet-va force-pushed the 01-29-feat_support_type_inference_with_new_test.extend_syntax branch from 3d73ede to a85eea4 Compare January 30, 2026 21:26
@sheremet-va
Copy link
Member Author

sheremet-va commented Jan 30, 2026

Damn it, snapshots fail on rolldown because it processes files differently 😡


By default, fixtures are initialized for each test. You can change this with the `scope` option to share fixtures across tests.

::: warning
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this warning section, @hi-ogawa. Would love to know what you think

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior looks reasonable to me. Note sure "force a new worker" behavior is better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "force a new worker" is what playwright does, otherwise worker fixtures ovrerride each other in different files

Copy link
Contributor

@hi-ogawa hi-ogawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestFixtures class looks great.

}

export function getTestFixture<Context = TestContext>(key: Context): FixtureItem[] {
export function getTestFixturesManager<Context = TestContext>(key: Context): TestFixtures {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: any reason renaming only getTestFixture -> getTestFixturesManager but keeping setTestFixture?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was one of the old attempts, I reverted to TestFixtures. There are now fixtures (the instance of the class) that have registrations

}

extend(runner: VitestRunner, userFixtures: Record<string, any>): TestFixtures {
const { suite } = getCurrentSuite()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: TestFixtures structure looks great, but TestFixtures.extend/override relying on getCurrentSuite feels odd in terms of state separation. How about pass it from argument?

Copy link
Member Author

@sheremet-va sheremet-va Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did pass it before, but moved it into the function directly in the last commit. We already call this function inside withFixtures anyway. The suite collection is intentionally global and fixture values rely on what suite they are in, so they are inherently tied. If we had a separate Runner class that does the collection, then it would make sense

>> db fixture teardown
>> aroundEach teardown"
>> aroundEach teardown
>> db fixture teardown"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this looks like what I suggested to happen in #9450 (comment) I forgot to double check the test.

}, { globals: true })
expect(stdout).toContain('basic.test.ts')
expect(stderr).toBe('')
expect(stderr).toMatchInlineSnapshot(`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't checking these test cases previously and the new extend/override behavior makes sense to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Two independent file-scoped test.extend with same key get mixed Support scoped fixtures with dependencies defined in the outer scope

3 participants