Skip to content
Closed
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
27 changes: 20 additions & 7 deletions packages/vitest/src/node/specifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,10 @@ export class VitestSpecifications {
return []
}

const transformCache = new Map<string, string[]>()
const testGraphs = await Promise.all(
specs.map(async (spec) => {
const deps = await this.getTestDependencies(spec)
const deps = await this.getTestDependencies(spec, transformCache)
return [spec, deps] as const
}),
)
Expand All @@ -169,19 +170,31 @@ export class VitestSpecifications {
return runningTests
}

private async getTestDependencies(spec: TestSpecification, deps = new Set<string>()): Promise<Set<string>> {
private async getTestDependencies(
spec: TestSpecification,
transformCache?: Map<string, string[]>,
deps = new Set<string>(),
): Promise<Set<string>> {
const addImports = async (project: TestProject, filepath: string) => {
if (deps.has(filepath)) {
return
}
deps.add(filepath)

const mod = project.vite.environments.ssr.moduleGraph.getModuleById(filepath)
const transformed = mod?.transformResult || await project.vite.environments.ssr.transformRequest(filepath)
if (!transformed) {
return
let dependencies: string[]
const cached = transformCache?.get(filepath)
if (cached) {
dependencies = cached
}
else {
const mod = project.vite.environments.ssr.moduleGraph.getModuleById(filepath)
const transformed = mod?.transformResult || await project.vite.environments.ssr.transformRequest(filepath)
if (!transformed) {
return
}
dependencies = [...transformed.deps || [], ...transformed.dynamicDeps || []]
transformCache?.set(filepath, dependencies)
}
const dependencies = [...transformed.deps || [], ...transformed.dynamicDeps || []]
await Promise.all(dependencies.map(async (dep) => {
const fsPath = dep.startsWith('/@fs/')
? dep.slice(isWindows ? 5 : 4)
Expand Down
1 change: 1 addition & 0 deletions test/cli/fixtures/related-multi-project/src/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = 'shared'
1 change: 1 addition & 0 deletions test/cli/fixtures/related-multi-project/src/unrelated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const other = 'unrelated'
6 changes: 6 additions & 0 deletions test/cli/fixtures/related-multi-project/tests/jsdom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { expect, test } from 'vitest'
import { value } from '../src/shared'

test('jsdom project uses shared module', () => {
expect(value).toBe('shared')
})
6 changes: 6 additions & 0 deletions test/cli/fixtures/related-multi-project/tests/node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { expect, test } from 'vitest'
import { value } from '../src/shared'

test('node project uses shared module', () => {
expect(value).toBe('shared')
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { expect, test } from 'vitest'
import { other } from '../src/unrelated'

test('unrelated test should not run', () => {
expect(other).toBe('unrelated')
})
22 changes: 22 additions & 0 deletions test/cli/fixtures/related-multi-project/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
projects: [
{
test: {
name: 'project-jsdom',
environment: 'jsdom',
include: ['./tests/jsdom.test.ts'],
},
},
{
test: {
name: 'project-node',
environment: 'node',
include: ['./tests/node.test.ts', './tests/unrelated.test.ts'],
},
},
],
},
})
38 changes: 38 additions & 0 deletions test/cli/test/related-multi-project.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { resolve } from 'pathe'
import { describe, expect, it } from 'vitest'
import { runVitest } from '../../test-utils'

// Regression test for https://github.com/vitest-dev/vitest/issues/9855
// Verifies that `vitest related` correctly resolves dependencies across
// multiple projects. The cache poisoning aspect of the bug (shared plugin
// caches corrupted by redundant transforms) requires @vitejs/plugin-vue
// with browser mode to reproduce and was verified against a real project.
describe('related with multiple projects', () => {
it('correctly finds related tests across multiple projects', async () => {
const root = resolve(import.meta.dirname, '../fixtures/related-multi-project')

const { stdout, stderr } = await runVitest({
root,
related: resolve(root, 'src/shared.ts'),
passWithNoTests: true,
})

expect(stderr).toBe('')
expect(stdout).toContain('project-jsdom')
expect(stdout).toContain('project-node')
expect(stdout).toContain('2 passed')
})

it('does not run unrelated tests', async () => {
const root = resolve(import.meta.dirname, '../fixtures/related-multi-project')

const { stdout, stderr } = await runVitest({
root,
related: resolve(root, 'src/shared.ts'),
passWithNoTests: true,
})

expect(stderr).toBe('')
expect(stdout).not.toContain('unrelated')
})
})
Loading