Skip to content
Open
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
6 changes: 6 additions & 0 deletions docs/guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ tests/test1.test.ts
tests/test2.test.ts
```

Use `--related` to list only the tests that import the specified source files:

```bash
vitest list --related --filesOnly src/index.ts src/utils.ts
```

Since Vitest 4.1, you may pass `--static-parse` to [parse test files](/api/advanced/vitest#parsespecifications) instead of running them to collect tests. Vitest parses test files with limited concurrency, defaulting to `os.availableParallelism()`. You can change it via the `--static-parse-concurrency` option.

### `vitest doctor`
Expand Down
12 changes: 11 additions & 1 deletion packages/vitest/src/node/cli/cac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,13 @@ export function createCLI(options: CliParseOptions = {}): CAC {
addCliOptions(
cli
.command('list [...filters]', undefined, options)
.action((filters, options) => collect(filters, options)),
.action((filters, options) => {
if (options.related === true) {
const { related, ...cliOptions } = options
return collect([], { ...cliOptions, related: filters })
}
return collect(filters, options)
}),
collectCliOptionsConfig,
)

Expand Down Expand Up @@ -254,6 +260,10 @@ export function parseCLI(argv: string | string[], config: CliParseOptions = {}):
options.passWithNoTests ??= true
args = []
}
if (arrayArgs[2] === 'list' && options.related === true) {
options.related = args
args = []
}
return {
filter: args as string[],
options,
Expand Down
6 changes: 5 additions & 1 deletion packages/vitest/src/node/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type CLIOptions<Config extends object> = {
}

type VitestCLIOptions = CLIOptions<CliOptions>
type CollectCliOptions = Omit<CliOptions, 'related'> & { related?: boolean }

const apiConfig: (port: number) => CLIOptions<ApiConfig> = (port: number) => ({
port: {
Expand Down Expand Up @@ -1013,7 +1014,7 @@ export const cliOptionsConfig: VitestCLIOptions = {
taskTitleValueFormatTruncate: null,
}

export const collectCliOptionsConfig: VitestCLIOptions = {
export const collectCliOptionsConfig: CLIOptions<CollectCliOptions> = {
...cliOptionsConfig,
json: {
description: 'Print collected tests as JSON or write to a file (Default: false)',
Expand All @@ -1029,6 +1030,9 @@ export const collectCliOptionsConfig: VitestCLIOptions = {
description: 'How many tests to process at the same time (default: os.availableParallelism())',
argument: '<limit>',
},
related: {
description: 'Print only tests related to the specified source files',
},
changed: {
description: 'Print only tests that are affected by the changed files (default: `false`)',
argument: '[since]',
Expand Down
147 changes: 147 additions & 0 deletions test/e2e/test/list-related.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { expect, test } from 'vitest'
import { replaceRoot, runInlineTests, runVitestCli } from '#test-utils'

const structure = {
'src/shared.ts': 'export const shared = true',
'src/intermediate.ts': `
export { shared } from './shared'
`,
'src/other.ts': 'export const other = true',
'src/unrelated.ts': 'export const unrelated = true',
'tests/direct.test.ts': `
import { expect, test } from 'vitest'
import { shared } from '../src/shared'

test('direct dependency', () => {
expect(shared).toBe(true)
})
`,
'tests/transitive.test.ts': `
import { expect, test } from 'vitest'
import { shared } from '../src/intermediate'

test('transitive dependency', () => {
expect(shared).toBe(true)
})
`,
'tests/other.test.ts': `
import { expect, test } from 'vitest'
import { other } from '../src/other'

test('other dependency', () => {
expect(other).toBe(true)
})
`,
'tests/unrelated.test.ts': `
import { expect, test } from 'vitest'
import { unrelated } from '../src/unrelated'

test('unrelated dependency', () => {
expect(unrelated).toBe(true)
})
`,
}

async function setupRelatedTests() {
const result = await runInlineTests(structure)

expect(result.stderr).toBe('')
expect(result.testTree()).toMatchInlineSnapshot(`
{
"tests/direct.test.ts": {
"direct dependency": "passed",
},
"tests/other.test.ts": {
"other dependency": "passed",
},
"tests/transitive.test.ts": {
"transitive dependency": "passed",
},
"tests/unrelated.test.ts": {
"unrelated dependency": "passed",
},
}
`)

return result
}

test('list --related includes direct and transitive dependents', async () => {
const { root } = await setupRelatedTests()
const { stdout, stderr, exitCode } = await runVitestCli(
'list',
`--root=${root}`,
'--related',
'src/shared.ts',
)

expect(stderr).toBe('')
expect(stdout).toMatchInlineSnapshot(`
"tests/direct.test.ts > direct dependency
tests/transitive.test.ts > transitive dependency
"
`)
expect(exitCode).toBe(0)
})

test('list --related combines multiple source files', async () => {
const { root } = await setupRelatedTests()
const { stdout, stderr, exitCode } = await runVitestCli(
'list',
`--root=${root}`,
'--related',
'src/shared.ts',
'src/other.ts',
)

expect(stderr).toBe('')
expect(stdout).toMatchInlineSnapshot(`
"tests/direct.test.ts > direct dependency
tests/other.test.ts > other dependency
tests/transitive.test.ts > transitive dependency
"
`)
expect(exitCode).toBe(0)
})

test('list --related supports files-only and JSON output', async () => {
const { root } = await setupRelatedTests()
const filesResult = await runVitestCli(
'list',
`--root=${root}`,
'--related',
'--filesOnly',
'src/shared.ts',
)
const jsonResult = await runVitestCli(
'list',
`--root=${root}`,
'--related',
'src/shared.ts',
'--json',
)

expect(filesResult.stderr).toBe('')
expect(filesResult.stdout).toMatchInlineSnapshot(`
"tests/direct.test.ts
tests/transitive.test.ts
"
`)
expect(filesResult.exitCode).toBe(0)

expect(jsonResult.stderr).toBe('')
expect(replaceRoot(jsonResult.stdout, root)).toMatchInlineSnapshot(`
"[
{
"name": "direct dependency",
"file": "<root>/tests/direct.test.ts"
},
{
"name": "transitive dependency",
"file": "<root>/tests/transitive.test.ts"
}
]
"
`)
expect(jsonResult.exitCode).toBe(0)
})
17 changes: 17 additions & 0 deletions test/unit/test/cli-test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,23 @@ test('public parseCLI works correctly', () => {
'color': true,
},
})
expect(parseCLI('vitest list --related ./source-a.ts ./source-b.ts')).toEqual({
filter: [],
options: {
'related': ['./source-a.ts', './source-b.ts'],
'--': [],
'color': true,
},
})
expect(parseCLI('vitest list --related --filesOnly ./source-a.ts ./source-b.ts')).toEqual({
filter: [],
options: {
'related': ['./source-a.ts', './source-b.ts'],
'filesOnly': true,
'--': [],
'color': true,
},
})

expect(parseCLI('vitest --coverage --browser=chrome')).toEqual({
filter: [],
Expand Down
Loading