Skip to content

Commit dbbbe43

Browse files
authored
feat!: remove the empty suite from the runner (#5435)
1 parent 2a80e95 commit dbbbe43

File tree

27 files changed

+694
-221
lines changed

27 files changed

+694
-221
lines changed

packages/runner/src/collect.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { relative } from 'pathe'
22
import { processError } from '@vitest/utils/error'
3-
import type { File } from './types'
3+
import type { File, SuiteHooks } from './types'
44
import type { VitestRunner } from './types/runner'
55
import { calculateSuiteHash, generateHash, interpretTaskModes, someTasksAreOnly } from './utils/collect'
6-
import { clearCollectorContext, getDefaultSuite } from './suite'
6+
import { clearCollectorContext, createSuiteHooks, getDefaultSuite } from './suite'
77
import { getHooks, setHooks } from './map'
88
import { collectorContext } from './context'
99
import { runSetupFiles } from './setup'
@@ -26,7 +26,9 @@ export async function collectTests(paths: string[], runner: VitestRunner): Promi
2626
tasks: [],
2727
meta: Object.create(null),
2828
projectName: config.name,
29+
file: undefined!,
2930
}
31+
file.file = file
3032

3133
clearCollectorContext(filepath, runner)
3234

@@ -41,24 +43,27 @@ export async function collectTests(paths: string[], runner: VitestRunner): Promi
4143

4244
const defaultTasks = await getDefaultSuite().collect(file)
4345

44-
setHooks(file, getHooks(defaultTasks))
46+
const fileHooks = createSuiteHooks()
47+
mergeHooks(fileHooks, getHooks(defaultTasks))
4548

4649
for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) {
47-
if (c.type === 'test') {
48-
file.tasks.push(c)
49-
}
50-
else if (c.type === 'custom') {
51-
file.tasks.push(c)
52-
}
53-
else if (c.type === 'suite') {
50+
if (c.type === 'test' || c.type === 'custom' || c.type === 'suite') {
5451
file.tasks.push(c)
5552
}
5653
else if (c.type === 'collector') {
5754
const suite = await c.collect(file)
58-
if (suite.name || suite.tasks.length)
55+
if (suite.name || suite.tasks.length) {
56+
mergeHooks(fileHooks, getHooks(suite))
5957
file.tasks.push(suite)
58+
}
59+
}
60+
else {
61+
// check that types are exhausted
62+
c satisfies never
6063
}
6164
}
65+
66+
setHooks(file, fileHooks)
6267
file.collectDuration = now() - collectStart
6368
}
6469
catch (e) {
@@ -74,8 +79,23 @@ export async function collectTests(paths: string[], runner: VitestRunner): Promi
7479
const hasOnlyTasks = someTasksAreOnly(file)
7580
interpretTaskModes(file, config.testNamePattern, hasOnlyTasks, false, config.allowOnly)
7681

82+
file.tasks.forEach((task) => {
83+
// task.suite refers to the internal default suite object
84+
// it should not be reported
85+
if (task.suite?.id === '')
86+
delete task.suite
87+
})
7788
files.push(file)
7889
}
7990

8091
return files
8192
}
93+
94+
function mergeHooks(baseHooks: SuiteHooks, hooks: SuiteHooks): SuiteHooks {
95+
for (const _key in hooks) {
96+
const key = _key as keyof SuiteHooks
97+
baseHooks[key].push(...hooks[key] as any)
98+
}
99+
100+
return baseHooks
101+
}

packages/runner/src/run.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,14 @@ export async function callSuiteHook<T extends keyof SuiteHooks>(
5757
const sequence = runner.config.sequence.hooks
5858

5959
const callbacks: HookCleanupCallback[] = []
60-
if (name === 'beforeEach' && suite.suite) {
60+
// stop at file level
61+
const parentSuite: Suite | null = 'filepath' in suite
62+
? null
63+
: (suite.suite || suite.file)
64+
65+
if (name === 'beforeEach' && parentSuite) {
6166
callbacks.push(
62-
...await callSuiteHook(suite.suite, currentTask, name, runner, args),
67+
...await callSuiteHook(parentSuite, currentTask, name, runner, args),
6368
)
6469
}
6570

@@ -77,9 +82,9 @@ export async function callSuiteHook<T extends keyof SuiteHooks>(
7782

7883
updateSuiteHookState(currentTask, name, 'pass', runner)
7984

80-
if (name === 'afterEach' && suite.suite) {
85+
if (name === 'afterEach' && parentSuite) {
8186
callbacks.push(
82-
...await callSuiteHook(suite.suite, currentTask, name, runner, args),
87+
...await callSuiteHook(parentSuite, currentTask, name, runner, args),
8388
)
8489
}
8590

@@ -150,6 +155,8 @@ export async function runTest(test: Test | Custom, runner: VitestRunner) {
150155

151156
setCurrentTest(test)
152157

158+
const suite = test.suite || test.file
159+
153160
const repeats = test.repeats ?? 0
154161
for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) {
155162
const retry = test.retry ?? 0
@@ -160,7 +167,7 @@ export async function runTest(test: Test | Custom, runner: VitestRunner) {
160167

161168
test.result.repeatCount = repeatCount
162169

163-
beforeEachCleanups = await callSuiteHook(test.suite, test, 'beforeEach', runner, [test.context, test.suite])
170+
beforeEachCleanups = await callSuiteHook(suite, test, 'beforeEach', runner, [test.context, suite])
164171

165172
if (runner.runTask) {
166173
await runner.runTask(test)
@@ -202,7 +209,7 @@ export async function runTest(test: Test | Custom, runner: VitestRunner) {
202209
}
203210

204211
try {
205-
await callSuiteHook(test.suite, test, 'afterEach', runner, [test.context, test.suite])
212+
await callSuiteHook(suite, test, 'afterEach', runner, [test.context, suite])
206213
await callCleanupHooks(beforeEachCleanups)
207214
await callFixtureCleanup(test.context)
208215
}

packages/runner/src/suite.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,15 @@ export function getRunner() {
4040
return runner
4141
}
4242

43+
function createDefaultSuite(runner: VitestRunner) {
44+
const config = runner.config.sequence
45+
const api = config.shuffle ? suite.shuffle : suite
46+
return api('', { concurrent: config.concurrent }, () => {})
47+
}
48+
4349
export function clearCollectorContext(filepath: string, currentRunner: VitestRunner) {
4450
if (!defaultSuite)
45-
defaultSuite = currentRunner.config.sequence.shuffle ? suite.shuffle('') : currentRunner.config.sequence.concurrent ? suite.concurrent('') : suite('')
51+
defaultSuite = createDefaultSuite(currentRunner)
4652
runner = currentRunner
4753
currentTestFilepath = filepath
4854
collectorContext.tasks.length = 0
@@ -121,6 +127,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
121127
fails: options.fails,
122128
context: undefined!,
123129
type: 'custom',
130+
file: undefined!,
124131
retry: options.retry ?? runner.config.retry,
125132
repeats: options.repeats,
126133
mode: options.only ? 'only' : options.skip ? 'skip' : options.todo ? 'todo' : 'run',
@@ -211,10 +218,10 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
211218
name,
212219
mode,
213220
each,
221+
file: undefined!,
214222
shuffle,
215223
tasks: [],
216224
meta: Object.create(null),
217-
projectName: '',
218225
}
219226

220227
if (runner && includeLocation && runner.config.includeTaskLocation) {
@@ -236,7 +243,10 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
236243
initSuite(false)
237244
}
238245

239-
async function collect(file?: File) {
246+
async function collect(file: File) {
247+
if (!file)
248+
throw new TypeError('File is required to collect tasks.')
249+
240250
factoryQueue.length = 0
241251
if (factory)
242252
await runWithSuite(collector, () => factory(test))
@@ -251,8 +261,7 @@ function createSuiteCollector(name: string, factory: SuiteFactory = () => { }, m
251261

252262
allChildren.forEach((task) => {
253263
task.suite = suite
254-
if (file)
255-
task.file = file
264+
task.file = file
256265
})
257266

258267
return suite

packages/runner/src/types/tasks.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export interface TaskBase {
1414
concurrent?: boolean
1515
shuffle?: boolean
1616
suite?: Suite
17-
file?: File
1817
result?: TaskResult
1918
retry?: number
2019
repeats?: number
@@ -25,7 +24,7 @@ export interface TaskBase {
2524
}
2625

2726
export interface TaskPopulated extends TaskBase {
28-
suite: Suite
27+
file: File
2928
pending?: boolean
3029
result?: TaskResult
3130
fails?: boolean
@@ -54,14 +53,14 @@ export interface TaskResult {
5453
export type TaskResultPack = [id: string, result: TaskResult | undefined, meta: TaskMeta]
5554

5655
export interface Suite extends TaskBase {
56+
file: File
5757
type: 'suite'
5858
tasks: Task[]
59-
filepath?: string
60-
projectName: string
6159
}
6260

6361
export interface File extends Suite {
6462
filepath: string
63+
projectName: string
6564
collectDuration?: number
6665
setupDuration?: number
6766
}
@@ -301,7 +300,7 @@ export interface SuiteCollector<ExtraContext = {}> {
301300
test: TestAPI<ExtraContext>
302301
tasks: (Suite | Custom<ExtraContext> | Test<ExtraContext> | SuiteCollector<ExtraContext>)[]
303302
task: (name: string, options?: TaskCustomOptions) => Custom<ExtraContext>
304-
collect: (file?: File) => Promise<Suite>
303+
collect: (file: File) => Promise<Suite>
305304
clear: () => void
306305
on: <T extends keyof SuiteHooks<ExtraContext>>(name: T, ...fn: SuiteHooks<ExtraContext>[T]) => void
307306
}

packages/runner/src/utils/tasks.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,14 @@ export function getNames(task: Task) {
4848
const names = [task.name]
4949
let current: Task | undefined = task
5050

51-
while (current?.suite || current?.file) {
52-
current = current.suite || current.file
51+
while (current?.suite) {
52+
current = current.suite
5353
if (current?.name)
5454
names.unshift(current.name)
5555
}
5656

57+
if (current !== task.file)
58+
names.unshift(task.file.name)
59+
5760
return names
5861
}

packages/vitest/src/integrations/chai/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { getCurrentTest } from '@vitest/runner'
77
import { ASYMMETRIC_MATCHERS_OBJECT, GLOBAL_EXPECT, addCustomEqualityTesters, getState, setState } from '@vitest/expect'
88
import type { Assertion, ExpectStatic } from '@vitest/expect'
99
import type { MatcherState } from '../../types/chai'
10-
import { getFullName } from '../../utils/tasks'
11-
import { getCurrentEnvironment } from '../../utils/global'
10+
import { getTestName } from '../../utils/tasks'
11+
import { getCurrentEnvironment, getWorkerState } from '../../utils/global'
1212

1313
export function createExpect(test?: TaskPopulated) {
1414
const expect = ((value: any, message?: string): Assertion => {
@@ -31,6 +31,7 @@ export function createExpect(test?: TaskPopulated) {
3131
// @ts-expect-error global is not typed
3232
const globalState = getState(globalThis[GLOBAL_EXPECT]) || {}
3333

34+
const testPath = getTestFile(test)
3435
setState<MatcherState>({
3536
// this should also add "snapshotState" that is added conditionally
3637
...globalState,
@@ -40,8 +41,8 @@ export function createExpect(test?: TaskPopulated) {
4041
expectedAssertionsNumber: null,
4142
expectedAssertionsNumberErrorGen: null,
4243
environment: getCurrentEnvironment(),
43-
testPath: test ? test.suite.file?.filepath : globalState.testPath,
44-
currentTestName: test ? getFullName(test as Test) : globalState.currentTestName,
44+
testPath,
45+
currentTestName: test ? getTestName(test as Test) : globalState.currentTestName,
4546
}, expect)
4647

4748
// @ts-expect-error untyped
@@ -89,6 +90,13 @@ export function createExpect(test?: TaskPopulated) {
8990
return expect
9091
}
9192

93+
function getTestFile(test?: TaskPopulated) {
94+
if (test)
95+
return test.file.filepath
96+
const state = getWorkerState()
97+
return state.filepath
98+
}
99+
92100
const globalExpect = createExpect()
93101

94102
Object.defineProperty(globalThis, GLOBAL_EXPECT, {

packages/vitest/src/integrations/snapshot/chai.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => {
4242
if (!test)
4343
return {}
4444
return {
45-
filepath: test.file?.filepath,
45+
filepath: test.file.filepath,
4646
name: getNames(test).slice(1).join(' > '),
4747
}
4848
}

packages/vitest/src/node/reporters/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ export abstract class BaseReporter implements Reporter {
326326

327327
logger.log(`\n${c.cyan(c.inverse(c.bold(' BENCH ')))} ${c.cyan('Summary')}\n`)
328328
for (const bench of topBenches) {
329-
const group = bench.suite
329+
const group = bench.suite || bench.file
330330
if (!group)
331331
continue
332332
const groupName = getFullName(group, c.dim(' > '))

packages/vitest/src/node/reporters/junit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ export class JUnitReporter implements Reporter {
248248
// NOTE: not used in JUnitReporter
249249
context: null as any,
250250
suite: null as any,
251+
file: null as any,
251252
} satisfies Task)
252253
}
253254

packages/vitest/src/node/reporters/renderers/listRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ function renderTree(tasks: Task[], options: ListRendererOptions, level = 0, maxR
9696
let suffix = ''
9797
let prefix = ` ${getStateSymbol(task)} `
9898

99-
if (level === 0 && task.type === 'suite' && task.projectName)
99+
if (level === 0 && task.type === 'suite' && 'projectName' in task)
100100
prefix += formatProjectName(task.projectName)
101101

102102
if (task.type === 'test' && task.result?.retryCount && task.result.retryCount > 0)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy