Skip to content

Commit c853126

Browse files
authored
fix(browser): correctly run in-source tests in the browser (#6440)
1 parent 7188709 commit c853126

File tree

9 files changed

+70
-13
lines changed

9 files changed

+70
-13
lines changed

packages/browser/src/client/tester/tester.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,17 @@ async function executeTests(method: 'run' | 'collect', files: string[]) {
140140
debug('prepare time', state.durations.prepare, 'ms')
141141

142142
try {
143-
await setupCommonEnv(config)
144-
await startCoverageInsideWorker(config.coverage, executor)
143+
await Promise.all([
144+
setupCommonEnv(config),
145+
startCoverageInsideWorker(config.coverage, executor),
146+
(async () => {
147+
const VitestIndex = await import('vitest')
148+
Object.defineProperty(window, '__vitest_index__', {
149+
value: VitestIndex,
150+
enumerable: false,
151+
})
152+
})(),
153+
])
145154

146155
for (const file of files) {
147156
state.filepath = file
@@ -168,7 +177,13 @@ async function executeTests(method: 'run' | 'collect', files: string[]) {
168177
}, 'Cleanup Error')
169178
}
170179
state.environmentTeardownRun = true
171-
await stopCoverageInsideWorker(config.coverage, executor)
180+
await stopCoverageInsideWorker(config.coverage, executor).catch((error) => {
181+
client.rpc.onUnhandledError({
182+
name: error.name,
183+
message: error.message,
184+
stack: String(error.stack),
185+
}, 'Coverage Error').catch(() => {})
186+
})
172187

173188
debug('finished running tests')
174189
done(files)

packages/browser/src/node/plugin.ts

+22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { type Plugin, coverageConfigDefaults } from 'vitest/config'
1010
import { toArray } from '@vitest/utils'
1111
import { defaultBrowserPort } from 'vitest/config'
1212
import { dynamicImportPlugin } from '@vitest/mocker/node'
13+
import MagicString from 'magic-string'
1314
import BrowserContext from './plugins/pluginContext'
1415
import type { BrowserServer } from './server'
1516
import { resolveOrchestrator } from './serverOrchestrator'
@@ -349,6 +350,22 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
349350
}
350351
},
351352
},
353+
{
354+
name: 'vitest:browser:in-source-tests',
355+
transform(code, id) {
356+
if (!project.isTestFile(id) || !code.includes('import.meta.vitest')) {
357+
return
358+
}
359+
const s = new MagicString(code, { filename: cleanUrl(id) })
360+
s.prepend(
361+
`import.meta.vitest = __vitest_index__;\n`,
362+
)
363+
return {
364+
code: s.toString(),
365+
map: s.generateMap({ hires: true }),
366+
}
367+
},
368+
},
352369
// TODO: remove this when @testing-library/vue supports ESM
353370
{
354371
name: 'vitest:browser:support-testing-library',
@@ -445,3 +462,8 @@ function resolveCoverageFolder(project: WorkspaceProject) {
445462

446463
return [resolve(root, subdir), `/${basename(root)}/${subdir}/`]
447464
}
465+
466+
const postfixRE = /[?#].*$/
467+
function cleanUrl(url: string): string {
468+
return url.replace(postfixRE, '')
469+
}

packages/ui/client/composables/explorer/collector.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ function collectData(summary: CollectorInfo) {
304304
file.prepareDuration = f.prepareDuration
305305
file.environmentLoad = f.environmentLoad
306306
file.collectDuration = f.collectDuration
307-
file.duration = f.result?.duration
307+
file.duration = f.result?.duration != null ? Math.round(f.result?.duration) : undefined
308308
file.state = f.result?.state
309309
}
310310
time += Math.max(0, f.collectDuration || 0)

packages/ui/client/composables/explorer/utils.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function createOrUpdateFileNode(
6969
tasks: [],
7070
typecheck: !!file.meta && 'typecheck' in file.meta,
7171
indent: 0,
72-
duration: file.result?.duration,
72+
duration: file.result?.duration != null ? Math.round(file.result?.duration) : undefined,
7373
filepath: file.filepath,
7474
projectName: file.projectName || '',
7575
projectNameColor: getProjectNameColor(file.projectName),
@@ -132,6 +132,9 @@ export function createOrUpdateNode(
132132
) {
133133
const node = explorerTree.nodes.get(parentId) as ParentTreeNode | undefined
134134
let taskNode: UITaskTreeNode | undefined
135+
const duration = task.result?.duration != null
136+
? Math.round(task.result?.duration)
137+
: undefined
135138
if (node) {
136139
taskNode = explorerTree.nodes.get(task.id)
137140
if (taskNode) {
@@ -141,7 +144,7 @@ export function createOrUpdateNode(
141144
}
142145

143146
taskNode.mode = task.mode
144-
taskNode.duration = task.result?.duration
147+
taskNode.duration = duration
145148
taskNode.state = task.result?.state
146149
}
147150
else {
@@ -156,7 +159,7 @@ export function createOrUpdateNode(
156159
expandable: false,
157160
expanded: false,
158161
indent: node.indent + 1,
159-
duration: task.result?.duration,
162+
duration,
160163
state: task.result?.state,
161164
} as TestTreeNode | CustomTestTreeNode
162165
}
@@ -174,7 +177,7 @@ export function createOrUpdateNode(
174177
children: new Set(),
175178
tasks: [],
176179
indent: node.indent + 1,
177-
duration: task.result?.duration,
180+
duration,
178181
state: task.result?.state,
179182
} as SuiteTreeNode
180183
}

test/browser/specs/runner.test.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,21 @@ describe('running browser tests', async () => {
2323
console.error(stderr)
2424
})
2525

26-
expect(browserResultJson.testResults).toHaveLength(18)
27-
expect(passedTests).toHaveLength(16)
26+
expect(browserResultJson.testResults).toHaveLength(19)
27+
expect(passedTests).toHaveLength(17)
2828
expect(failedTests).toHaveLength(2)
2929

3030
expect(stderr).not.toContain('has been externalized for browser compatibility')
3131
expect(stderr).not.toContain('Unhandled Error')
3232
})
3333

34+
test('runs in-source tests', () => {
35+
expect(stdout).toContain('src/actions.ts')
36+
const actionsTest = passedTests.find(t => t.name.includes('/actions.ts'))
37+
expect(actionsTest).toBeDefined()
38+
expect(actionsTest.assertionResults).toHaveLength(1)
39+
})
40+
3441
test('correctly prints error', () => {
3542
expect(stderr).toContain('expected 1 to be 2')
3643
expect(stderr).toMatch(/- 2\s+\+ 1/)

test/browser/src/actions.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
export function plus(a: number, b: number) {
22
return a + b
33
}
4+
5+
if (import.meta.vitest) {
6+
const { test, expect } = import.meta.vitest
7+
8+
test('in-source plus works correctly', () => {
9+
expect(plus(1, 2)).toBe(3)
10+
})
11+
}

test/browser/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"types": [
1010
"vite/client",
1111
"@vitest/browser/providers/playwright",
12-
"vitest-browser-react"
12+
"vitest-browser-react",
13+
"vitest/import-meta"
1314
],
1415
"esModuleInterop": true
1516
}

test/browser/vitest.config.mts

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default defineConfig({
2828
},
2929
test: {
3030
include: ['test/**.test.{ts,js,tsx}'],
31+
includeSource: ['src/*.ts'],
3132
// having a snapshot environment doesn't affect browser tests
3233
snapshotEnvironment: './custom-snapshot-env.ts',
3334
browser: {

test/browser/vitest.config.unit.mts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default defineConfig({
88
singleFork: true,
99
},
1010
},
11-
hookTimeout: process.env.CI ? 120_000 : 10_000,
12-
testTimeout: process.env.CI ? 120_000 : 10_000,
11+
hookTimeout: process.env.CI ? 120_000 : 20_000,
12+
testTimeout: process.env.CI ? 120_000 : 20_000,
1313
},
1414
})

0 commit comments

Comments
 (0)