Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/api/advanced/test-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ This is a Vite's [`DevEnvironment`](https://vite.dev/guide/api-environment) that
## toTestSpecification <Version>4.1.0</Version> {#totestspecification}

```ts
function toTestSpecification(): TestSpecification
function toTestSpecification(testCases?: TestCase[]): TestSpecification
```

Returns a new [test specification](/api/advanced/test-specification) that can be used to filter or run this specific test module.

It accepts an optional array of test cases that should be filtered.
2 changes: 1 addition & 1 deletion docs/config/coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ To preview the coverage report in the output of [HTML reporter](/guide/reporters
- **Available for providers:** `'v8' | 'istanbul'`
- **CLI:** `--coverage.reporter=<reporter>`, `--coverage.reporter=<reporter1> --coverage.reporter=<reporter2>`

Coverage reporters to use. See [istanbul documentation](https://istanbul.js.org/docs/advanced/alternative-reporters/) for detailed list of all reporters. See [`@types/istanbul-reporter`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/276d95e4304b3670eaf6e8e5a7ea9e265a14e338/types/istanbul-reports/index.d.ts) for details about reporter specific options.
Coverage reporters to use. See [istanbul documentation](https://istanbul.js.org/docs/advanced/alternative-reporters/) for detailed list of all reporters. See [`@types/istanbul-reports`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/276d95e4304b3670eaf6e8e5a7ea9e265a14e338/types/istanbul-reports/index.d.ts) for details about reporter specific options.

The reporter has three different types:

Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/node/ast-collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function astParseFile(filepath: string, code: string) {
const property = callee?.property?.name
let mode = !property || property === name ? 'run' : property
// they will be picked up in the next iteration
if (['each', 'for', 'skipIf', 'runIf', 'extend', 'scoped'].includes(mode)) {
if (['each', 'for', 'skipIf', 'runIf', 'extend', 'scoped', 'override'].includes(mode)) {
return
}

Expand Down
4 changes: 2 additions & 2 deletions packages/vitest/src/node/reporters/reported-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,11 +509,11 @@ export class TestModule extends SuiteImplementation {
/**
* Returns a new test specification that can be used to filter or run this specific test module.
*/
public toTestSpecification(): TestSpecification {
public toTestSpecification(testCases?: TestCase[]): TestSpecification {
const isTypecheck = this.task.meta.typecheck === true
return this.project.createSpecification(
this.moduleId,
undefined,
testCases?.length ? { testIds: testCases.map(t => t.id) } : undefined,
isTypecheck ? 'typecheck' : undefined,
)
}
Expand Down
475 changes: 301 additions & 174 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions test/cli/deps/dep-invalid/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "test-dep-invalid",
"private": true
}
9 changes: 9 additions & 0 deletions test/cli/fixtures/invalid-package/mock-bad-dep.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @ts-expect-error no type
import * as dep from 'test-dep-invalid'
import { expect, test, vi } from 'vitest'

vi.mock('test-dep-invalid', () => ({ mocked: 'ok' }))

test('basic', () => {
expect(dep).toMatchObject({ mocked: 'ok' })
})
12 changes: 12 additions & 0 deletions test/cli/fixtures/invalid-package/mock-wrapper-and-bad-dep.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, test, vi } from 'vitest'
import { hello } from './wrapper.js'

vi.mock('test-dep-invalid', () => ({}))

vi.mock(import('./wrapper.js'), () => {
return { hello: () => 'mock-hello' }
})

test('basic', () => {
expect(hello()).toBe('mock-hello')
})
10 changes: 10 additions & 0 deletions test/cli/fixtures/invalid-package/mock-wrapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expect, test, vi } from 'vitest'
import { hello } from './wrapper.js'

vi.mock(import('./wrapper.js'), () => {
return { hello: () => 'mock-hello' }
})

test('basic', () => {
expect(hello()).toBe('mock-hello')
})
4 changes: 4 additions & 0 deletions test/cli/fixtures/invalid-package/wrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @ts-expect-error no type
import * as dep from 'test-dep-invalid'

export const hello = () => dep
1 change: 1 addition & 0 deletions test/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"flatted": "catalog:",
"obug": "^2.1.1",
"playwright": "catalog:",
"test-dep-invalid": "link:./deps/dep-invalid",
"tinyspy": "catalog:",
"typescript": "catalog:",
"unplugin-swc": "^1.5.9",
Expand Down
116 changes: 115 additions & 1 deletion test/cli/test/mocking.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import path from 'node:path'
import { expect, test } from 'vitest'
import { runInlineTests } from '../../test-utils'
import { rolldownVersion } from 'vitest/node'
import { runInlineTests, runVitest } from '../../test-utils'

test('setting resetMocks works if restoreMocks is also set', async () => {
const { stderr, testTree } = await runInlineTests({
Expand Down Expand Up @@ -39,3 +41,115 @@ test('spy is not called here', () => {
}
`)
})

test('invalid packages', async () => {
const { results, errorTree } = await runVitest({
root: path.join(import.meta.dirname, '../fixtures/invalid-package'),
})
const testModuleErrors = Object.fromEntries(
results.map(testModule => [
testModule.relativeModuleId,
testModule.errors().map(e => e.message),
]),
)

// requires Vite 8 for relaxed import analysis validataion
// https://github.com/vitejs/vite/pull/21601
if (rolldownVersion) {
expect(testModuleErrors).toMatchInlineSnapshot(`
{
"mock-bad-dep.test.ts": [],
"mock-wrapper-and-bad-dep.test.ts": [],
"mock-wrapper.test.ts": [],
}
`)
expect(errorTree()).toMatchInlineSnapshot(`
{
"mock-bad-dep.test.ts": {
"basic": "passed",
},
"mock-wrapper-and-bad-dep.test.ts": {
"basic": "passed",
},
"mock-wrapper.test.ts": {
"basic": "passed",
},
}
`)
}
else {
expect(testModuleErrors).toMatchInlineSnapshot(`
{
"mock-bad-dep.test.ts": [
"Failed to resolve entry for package "test-dep-invalid". The package may have incorrect main/module/exports specified in its package.json.",
],
"mock-wrapper-and-bad-dep.test.ts": [
"Failed to resolve entry for package "test-dep-invalid". The package may have incorrect main/module/exports specified in its package.json.",
],
"mock-wrapper.test.ts": [
"Failed to resolve entry for package "test-dep-invalid". The package may have incorrect main/module/exports specified in its package.json.",
],
}
`)
expect(errorTree()).toMatchInlineSnapshot(`
{
"mock-bad-dep.test.ts": {},
"mock-wrapper-and-bad-dep.test.ts": {},
"mock-wrapper.test.ts": {},
}
`)
}
})

test('mocking modules with syntax error', async () => {
// TODO: manual mocked module still gets transformed so this is not supported yet.
const { errorTree, results } = await runInlineTests({
'./syntax-error.js': `syntax error`,
'./basic.test.js': /* ts */ `
import * as dep from './syntax-error.js'

vi.mock('./syntax-error.js', () => {
return { mocked: 'ok' }
})

test('can mock invalid module', () => {
expect(dep).toMatchObject({ mocked: 'ok' })
})
`,
})

const testModuleErrors = Object.fromEntries(
results.map(testModule => [
testModule.relativeModuleId,
testModule.errors().map(e => e.message),
]),
)
if (rolldownVersion) {
expect(testModuleErrors).toMatchInlineSnapshot(`
{
"basic.test.js": [
"Parse failure: Parse failed with 1 error:
Expected a semicolon or an implicit semicolon after a statement, but found none
1: syntax error
^
At file: /syntax-error.js:1:6",
],
}
`)
}
else {
expect(testModuleErrors).toMatchInlineSnapshot(`
{
"basic.test.js": [
"Parse failure: Expected ';', '}' or <eof>
At file: /syntax-error.js:1:7",
],
}
`)
}
expect(errorTree()).toMatchInlineSnapshot(`
{
"basic.test.js": {},
}
`)
})
51 changes: 51 additions & 0 deletions test/cli/test/static-collect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,57 @@ test('collects nested suites with custom test functions', async () => {
`)
})

test('ignores test.scoped and test.override', async () => {
const testModule = await collectTests(`
import { test as base } from 'vitest'

const test = base.extend({
fixture: async ({}, use) => {
await use('value')
},
})

test.scoped({ fixture: 'value' })
test.override({ fixture: 'value' })

describe('extended tests', () => {
test('uses extended test', () => {})
test.skip('skips extended test', () => {})
test.only('only extended test', () => {})
})
`)
expect(testModule).toMatchInlineSnapshot(`
{
"extended tests": {
"only extended test": {
"errors": [],
"fullName": "extended tests > only extended test",
"id": "-1732721377_0_2",
"location": "16:6",
"mode": "run",
"state": "pending",
},
"skips extended test": {
"errors": [],
"fullName": "extended tests > skips extended test",
"id": "-1732721377_0_1",
"location": "15:6",
"mode": "skip",
"state": "skipped",
},
"uses extended test": {
"errors": [],
"fullName": "extended tests > uses extended test",
"id": "-1732721377_0_0",
"location": "14:6",
"mode": "skip",
"state": "skipped",
},
},
}
`)
})

test('collects tests from test.extend', async () => {
const testModule = await collectTests(`
import { test as base } from 'vitest'
Expand Down
50 changes: 3 additions & 47 deletions test/snapshots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,10 @@

This directory contains integration tests for Vitest's snapshot functionality. It uses a meta-testing approach where integration tests programmatically run fixture tests to validate snapshot behavior.

## Directory Structure

```
test/snapshots/
├── test/ # Integration tests that validate snapshot features
│ └── fixtures/ # Test fixture files (copied to test-update/)
├── test-update/ # Generated directory - populated from fixtures
├── generate.mjs # Resets test-update/ from fixtures
└── vitest.config.ts # Test configuration
```

## Test Scripts

| Script | Purpose |
|--------|---------|
| `test` | Runs the complete test suite (all scripts below in sequence) |
| `test:generate` | Resets `test-update/` by copying fresh fixtures |
| `test:update` | Runs tests with `-u` flag to update existing snapshots |
| `test:update-new` | Runs with `CI=false` to create new snapshots |
| `test:update-none` | Runs with `CI=true` to validate without updates (strict mode) |
| `test:integration` | Runs the main integration tests in `test/` |

## How It Works

1. **`generate.mjs`** copies fixture files from `test/fixtures/test-update/` to `test-update/`
2. **`test:update*` scripts** run the fixture tests with different snapshot update modes
3. **`test:integration`** runs integration tests that use `runVitest()` to programmatically execute fixtures and assert on the results

This setup allows testing snapshot features like:
- Inline snapshots (`toMatchInlineSnapshot`)
- File-based snapshots (`toMatchFileSnapshot`)
- Snapshot update behavior with `-u` flag
- CI vs non-CI snapshot creation modes
- Custom serializers, soft assertions, retry logic, etc.

## Running Tests

```bash
# Run all snapshot tests
# Run all integration tests
pnpm test

# Or run individual stages
# - Reset fixtures
pnpm test:generate
# - Run integration tests only
pnpm test:integration
pnpm test:integration test/summary.test.ts

# Run one of fixtures directly
pnpm test:fixtures --root test/fixtures/summary
# Run one fixture directly
pnpm test --root test/fixtures/summary
```
10 changes: 0 additions & 10 deletions test/snapshots/generate.mjs

This file was deleted.

9 changes: 1 addition & 8 deletions test/snapshots/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,7 @@
"type": "module",
"private": true,
"scripts": {
"test": "pnpm run test:generate && pnpm run test:update && pnpm test:update-new && pnpm test:update-none && pnpm run test:integration",
"test:generate": "node ./generate.mjs",
"test:integration": "vitest run --dir test",
"test:update": "vitest run -u --dir test-update",
"test:update-none": "CI=true vitest run --dir test-update",
"test:update-new": "CI=false vitest run --dir test-update",
"test:fixtures": "vitest",
"coverage": "vitest run --coverage"
"test": "vitest"
},
"dependencies": {
"vitest": "workspace:*"
Expand Down
Loading
Loading