Skip to content

Commit d2a7d36

Browse files
authored
Merge branch 'main' into 02-05-feat_assertion_helper_to_filter_out_internal_stack_traces
2 parents 8483a9e + 89cbdae commit d2a7d36

12 files changed

Lines changed: 226 additions & 47 deletions

File tree

docs/guide/lifecycle.md

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,17 @@ The execution follows this order:
126126

127127
1. **File-level code** - All code outside `describe` blocks runs immediately
128128
2. **Test collection** - `describe` blocks are processed, and tests are registered as side effects of importing the test file
129-
3. **`beforeAll` hooks** - Run once before any tests in the suite
130-
4. **For each test:**
129+
3. **[`aroundAll`](/api/hooks#aroundall) hooks** - Wrap around all tests in the suite (must call `runSuite()`)
130+
4. **[`beforeAll`](/api/hooks#beforeall) hooks** - Run once before any tests in the suite
131+
5. **For each test:**
132+
- [`aroundEach`](/api/hooks#aroundeach) hooks wrap around the test (must call `runTest()`)
131133
- `beforeEach` hooks execute (in order defined, or based on [`sequence.hooks`](/config/sequence#sequence-hooks))
132134
- Test function executes
133135
- `afterEach` hooks execute (reverse order by default with `sequence.hooks: 'stack'`)
134136
- [`onTestFinished`](/api/hooks#ontestfinished) callbacks run (always in reverse order)
135137
- If test failed: [`onTestFailed`](/api/hooks#ontestfailed) callbacks run
136138
- Note: if `repeats` or `retry` are set, all of these steps are executed again
137-
5. **`afterAll` hooks** - Run once after all tests in the suite complete
139+
6. **[`afterAll`](/api/hooks#afterall) hooks** - Run once after all tests in the suite complete
138140

139141
**Example execution flow:**
140142

@@ -146,11 +148,25 @@ describe('User API', () => {
146148
// This runs immediately (collection phase)
147149
console.log('Suite defined')
148150

151+
aroundAll(async (runSuite) => {
152+
// Wraps around all tests in this suite
153+
console.log('aroundAll before')
154+
await runSuite()
155+
console.log('aroundAll after')
156+
})
157+
149158
beforeAll(() => {
150159
// Runs once before all tests in this suite
151160
console.log('beforeAll')
152161
})
153162

163+
aroundEach(async (runTest) => {
164+
// Wraps around each test
165+
console.log('aroundEach before')
166+
await runTest()
167+
console.log('aroundEach after')
168+
})
169+
154170
beforeEach(() => {
155171
// Runs before each test
156172
console.log('beforeEach')
@@ -180,29 +196,61 @@ describe('User API', () => {
180196
// Output:
181197
// File loaded
182198
// Suite defined
183-
// beforeAll
184-
// beforeEach
185-
// test 1
186-
// afterEach
187-
// beforeEach
188-
// test 2
189-
// afterEach
190-
// afterAll
199+
// aroundAll before
200+
// beforeAll
201+
// aroundEach before
202+
// beforeEach
203+
// test 1
204+
// afterEach
205+
// aroundEach after
206+
// aroundEach before
207+
// beforeEach
208+
// test 2
209+
// afterEach
210+
// aroundEach after
211+
// afterAll
212+
// aroundAll after
191213
```
192214

193215
#### Nested Suites
194216

195-
When using nested `describe` blocks, hooks follow a hierarchical pattern:
217+
When using nested `describe` blocks, hooks follow a hierarchical pattern. The `aroundAll` and `aroundEach` hooks wrap around their respective scopes, with parent hooks wrapping child hooks:
196218

197219
```ts
198220
describe('outer', () => {
221+
aroundAll(async (runSuite) => {
222+
console.log('outer aroundAll before')
223+
await runSuite()
224+
console.log('outer aroundAll after')
225+
})
226+
199227
beforeAll(() => console.log('outer beforeAll'))
228+
229+
aroundEach(async (runTest) => {
230+
console.log('outer aroundEach before')
231+
await runTest()
232+
console.log('outer aroundEach after')
233+
})
234+
200235
beforeEach(() => console.log('outer beforeEach'))
201236

202237
test('outer test', () => console.log('outer test'))
203238

204239
describe('inner', () => {
240+
aroundAll(async (runSuite) => {
241+
console.log('inner aroundAll before')
242+
await runSuite()
243+
console.log('inner aroundAll after')
244+
})
245+
205246
beforeAll(() => console.log('inner beforeAll'))
247+
248+
aroundEach(async (runTest) => {
249+
console.log('inner aroundEach before')
250+
await runTest()
251+
console.log('inner aroundEach after')
252+
})
253+
206254
beforeEach(() => console.log('inner beforeEach'))
207255

208256
test('inner test', () => console.log('inner test'))
@@ -216,18 +264,28 @@ describe('outer', () => {
216264
})
217265

218266
// Output:
219-
// outer beforeAll
220-
// outer beforeEach
221-
// outer test
222-
// outer afterEach
223-
// inner beforeAll
224-
// outer beforeEach
225-
// inner beforeEach
226-
// inner test
227-
// inner afterEach (with stack mode)
228-
// outer afterEach (with stack mode)
229-
// inner afterAll
230-
// outer afterAll
267+
// outer aroundAll before
268+
// outer beforeAll
269+
// outer aroundEach before
270+
// outer beforeEach
271+
// outer test
272+
// outer afterEach
273+
// outer aroundEach after
274+
// inner aroundAll before
275+
// inner beforeAll
276+
// outer aroundEach before
277+
// inner aroundEach before
278+
// outer beforeEach
279+
// inner beforeEach
280+
// inner test
281+
// inner afterEach
282+
// outer afterEach
283+
// inner aroundEach after
284+
// outer aroundEach after
285+
// inner afterAll
286+
// inner aroundAll after
287+
// outer afterAll
288+
// outer aroundAll after
231289
```
232290

233291
#### Concurrent Tests
@@ -278,7 +336,9 @@ Understanding where code executes is crucial for avoiding common pitfalls:
278336
| Global Setup | Main process | ❌ No (use `provide`/`inject`) | Once per Vitest run |
279337
| Setup Files | Worker (same as tests) | ✅ Yes | Before each test file |
280338
| File-level code | Worker | ✅ Yes | Once per test file |
339+
| `aroundAll` | Worker | ✅ Yes | Once per suite (wraps all tests) |
281340
| `beforeAll` / `afterAll` | Worker | ✅ Yes | Once per suite |
341+
| `aroundEach` | Worker | ✅ Yes | Per test (wraps each test) |
282342
| `beforeEach` / `afterEach` | Worker | ✅ Yes | Per test |
283343
| Test function | Worker | ✅ Yes | Once (or more with retries/repeats) |
284344
| Global Teardown | Main process | ❌ No | Once per Vitest run |

packages/utils/src/source-map.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ export class DecodedMap {
370370
this._decodedMemo = memoizedState()
371371
this.url = from
372372
this.resolvedSources = (sources || []).map(s =>
373-
resolve(s || '', from),
373+
resolve(from, '..', s || ''),
374374
)
375375
}
376376
}

packages/vitest/LICENSE.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,37 @@ Repository: egoist/cac
316316
317317
---------------------------------------
318318

319+
## convert-source-map
320+
License: MIT
321+
By: Thorsten Lorenz
322+
Repository: git://github.com/thlorenz/convert-source-map.git
323+
324+
> Copyright 2013 Thorsten Lorenz.
325+
> All rights reserved.
326+
>
327+
> Permission is hereby granted, free of charge, to any person
328+
> obtaining a copy of this software and associated documentation
329+
> files (the "Software"), to deal in the Software without
330+
> restriction, including without limitation the rights to use,
331+
> copy, modify, merge, publish, distribute, sublicense, and/or sell
332+
> copies of the Software, and to permit persons to whom the
333+
> Software is furnished to do so, subject to the following
334+
> conditions:
335+
>
336+
> The above copyright notice and this permission notice shall be
337+
> included in all copies or substantial portions of the Software.
338+
>
339+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
340+
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
341+
> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
342+
> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
343+
> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
344+
> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
345+
> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
346+
> OTHER DEALINGS IN THE SOFTWARE.
347+
348+
---------------------------------------
349+
319350
## empathic
320351
License: MIT
321352
By: Luke Edwards

packages/vitest/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@
138138
"@vitest/browser-webdriverio": "workspace:*",
139139
"@vitest/ui": "workspace:*",
140140
"happy-dom": "*",
141-
"jsdom": "*"
141+
"jsdom": "*",
142+
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
142143
},
143144
"peerDependenciesMeta": {
144145
"@edge-runtime/vm": {
@@ -167,6 +168,9 @@
167168
},
168169
"jsdom": {
169170
"optional": true
171+
},
172+
"vite": {
173+
"optional": false
170174
}
171175
},
172176
"dependencies": {
@@ -188,7 +192,7 @@
188192
"tinyexec": "^1.0.2",
189193
"tinyglobby": "catalog:",
190194
"tinyrainbow": "catalog:",
191-
"vite": "^6.0.0 || ^7.0.0",
195+
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0",
192196
"why-is-node-running": "^2.3.0"
193197
},
194198
"devDependencies": {
@@ -198,6 +202,7 @@
198202
"@jridgewell/trace-mapping": "catalog:",
199203
"@opentelemetry/api": "^1.9.0",
200204
"@sinonjs/fake-timers": "15.0.0",
205+
"@types/convert-source-map": "^2.0.3",
201206
"@types/estree": "catalog:",
202207
"@types/istanbul-lib-coverage": "catalog:",
203208
"@types/istanbul-reports": "catalog:",
@@ -210,6 +215,7 @@
210215
"acorn-walk": "catalog:",
211216
"birpc": "catalog:",
212217
"cac": "catalog:",
218+
"convert-source-map": "^2.0.0",
213219
"empathic": "^2.0.0",
214220
"flatted": "catalog:",
215221
"happy-dom": "^20.4.0",

packages/vitest/src/node/ast-collect.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ function astParseFile(filepath: string, code: string) {
161161
}
162162
else {
163163
message = code.slice(messageNode.start, messageNode.end)
164+
165+
if (message.endsWith('.name')) {
166+
message = message.slice(0, -5)
167+
}
164168
}
165169

166170
if (message.startsWith('0,')) {

packages/vitest/src/node/plugins/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite'
22
import type { ResolvedConfig, UserConfig } from '../types/config'
33
import { deepClone, deepMerge, notNullish } from '@vitest/utils/helpers'
4-
import { relative } from 'pathe'
4+
import { relative, resolve } from 'pathe'
55
import * as vite from 'vite'
66
import { defaultPort } from '../../constants'
77
import { configDefaults } from '../../defaults'
@@ -30,7 +30,7 @@ export async function VitestPlugin(
3030
const userConfig = deepMerge({}, options) as UserConfig
3131

3232
async function UIPlugin() {
33-
await vitest.packageInstaller.ensureInstalled('@vitest/ui', options.root || process.cwd(), vitest.version)
33+
await vitest.packageInstaller.ensureInstalled('@vitest/ui', resolve(options.root || process.cwd()), vitest.version)
3434
return (await import('@vitest/ui')).default(vitest)
3535
}
3636

packages/vitest/src/node/pool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ function resolveOptions(ctx: Vitest) {
281281
...conditions,
282282
'--experimental-import-meta-resolve',
283283
// https://github.com/vitest-dev/vitest/issues/8896
284-
...((globalThis as any).Deno ? [] : ['--require', suppressWarningsPath]),
284+
...((globalThis as any).Deno || process.versions.pnp ? [] : ['--require', suppressWarningsPath]),
285285
],
286286
env: {
287287
TEST: 'true',

packages/vitest/src/node/test-run.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
} from '@vitest/runner'
88
import type { TaskEventData, TestArtifact } from '@vitest/runner/types/tasks'
99
import type { SerializedError } from '@vitest/utils'
10+
import type { SourceMap } from 'rollup'
1011
import type { UserConsoleLog } from '../types/general'
1112
import type { Vitest } from './core'
1213
import type { TestProject } from './project'
@@ -15,11 +16,13 @@ import type { TestSpecification } from './test-specification'
1516
import type { TestRunEndReason } from './types/reporter'
1617
import assert from 'node:assert'
1718
import { createHash } from 'node:crypto'
18-
import { existsSync } from 'node:fs'
19+
import { existsSync, readFileSync } from 'node:fs'
1920
import { copyFile, mkdir, writeFile } from 'node:fs/promises'
21+
import path from 'node:path'
2022
import { isPrimitive } from '@vitest/utils/helpers'
2123
import { serializeValue } from '@vitest/utils/serialize'
2224
import { parseErrorStacktrace } from '@vitest/utils/source-map'
25+
import convertSourceMap from 'convert-source-map'
2326
import mime from 'mime/lite'
2427
import { basename, extname, resolve } from 'pathe'
2528

@@ -170,6 +173,18 @@ export class TestRun {
170173
else {
171174
error.stacks = parseErrorStacktrace(error, {
172175
frameFilter: project.config.onStackTrace,
176+
getSourceMap(file) {
177+
// This only handles external modules since
178+
// source map is already applied for inlined modules.
179+
// Module node exists due to Vitest fetch module,
180+
// but transformResult should be empty for external modules.
181+
const mod = project.vite.moduleGraph.getModuleById(file)
182+
if (!mod?.transformResult && existsSync(file)) {
183+
const code = readFileSync(file, 'utf-8')
184+
const result = extractSourcemapFromFile(code, file)
185+
return result
186+
}
187+
},
173188
})
174189
}
175190
})
@@ -298,3 +313,31 @@ function sanitizeFilePath(s: string): string {
298313
// eslint-disable-next-line no-control-regex
299314
return s.replace(/[\x00-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-')
300315
}
316+
317+
// based on vite
318+
// https://github.com/vitejs/vite/blob/84079a84ad94de4c1ef4f1bdb2ab448ff2c01196/packages/vite/src/node/server/sourcemap.ts#L149
319+
function extractSourcemapFromFile(
320+
code: string,
321+
filePath: string,
322+
): SourceMap | undefined {
323+
const map = (
324+
convertSourceMap.fromSource(code)
325+
|| (convertSourceMap.fromMapFileSource(
326+
code,
327+
createConvertSourceMapReadMap(filePath),
328+
))
329+
)?.toObject()
330+
return map
331+
}
332+
333+
function createConvertSourceMapReadMap(originalFileName: string) {
334+
return (filename: string) => {
335+
// convertSourceMap can detect invalid filename from comments.
336+
// fallback to empty source map to avoid errors.
337+
const targetPath = path.resolve(path.dirname(originalFileName), filename)
338+
if (existsSync(targetPath)) {
339+
return readFileSync(targetPath, 'utf-8')
340+
}
341+
return '{}'
342+
}
343+
}

0 commit comments

Comments
 (0)