From 06e6d3a167d1a99d1d6e9b3a97fd11913a015be5 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 12:45:25 +0100 Subject: [PATCH 01/16] refactor: merge TestProject with WorkspaceProject --- packages/browser/src/node/pool.ts | 18 +- packages/vitest/src/node/core.ts | 71 ++- packages/vitest/src/node/error.ts | 6 +- packages/vitest/src/node/globalSetup.ts | 9 + packages/vitest/src/node/logger.ts | 6 +- packages/vitest/src/node/plugins/workspace.ts | 6 +- packages/vitest/src/node/pool.ts | 8 +- packages/vitest/src/node/pools/forks.ts | 10 +- packages/vitest/src/node/pools/rpc.ts | 4 +- packages/vitest/src/node/pools/threads.ts | 10 +- packages/vitest/src/node/pools/typecheck.ts | 12 +- packages/vitest/src/node/pools/vmForks.ts | 10 +- packages/vitest/src/node/pools/vmThreads.ts | 10 +- .../src/node/{workspace.ts => project.ts} | 483 ++++++++++++------ .../src/node/reported-workspace-project.ts | 83 --- packages/vitest/src/node/reporters/blob.ts | 4 +- .../src/node/reporters/github-actions.ts | 4 +- packages/vitest/src/node/reporters/index.ts | 2 +- .../src/node/reporters/reported-tasks.ts | 29 +- packages/vitest/src/node/spec.ts | 11 +- packages/vitest/src/node/state.ts | 10 +- packages/vitest/src/node/types/browser.ts | 6 +- packages/vitest/src/node/types/config.ts | 3 +- .../src/node/workspace/resolveWorkspace.ts | 8 +- packages/vitest/src/public/node.ts | 5 +- packages/vitest/src/typecheck/collect.ts | 4 +- packages/vitest/src/typecheck/typechecker.ts | 4 +- 27 files changed, 473 insertions(+), 363 deletions(-) rename packages/vitest/src/node/{workspace.ts => project.ts} (56%) delete mode 100644 packages/vitest/src/node/reported-workspace-project.ts diff --git a/packages/browser/src/node/pool.ts b/packages/browser/src/node/pool.ts index 9079535bee75..42c8a9654710 100644 --- a/packages/browser/src/node/pool.ts +++ b/packages/browser/src/node/pool.ts @@ -1,4 +1,4 @@ -import type { BrowserProvider, ProcessPool, Vitest, WorkspaceProject, WorkspaceSpec } from 'vitest/node' +import type { BrowserProvider, ProcessPool, TestProject, TestSpecification, Vitest } from 'vitest/node' import crypto from 'node:crypto' import * as nodeos from 'node:os' import { relative } from 'pathe' @@ -9,7 +9,7 @@ const debug = createDebugger('vitest:browser:pool') async function waitForTests( method: 'run' | 'collect', contextId: string, - project: WorkspaceProject, + project: TestProject, files: string[], ) { const context = project.browser!.state.createAsyncContext(method, contextId, files) @@ -19,7 +19,7 @@ async function waitForTests( export function createBrowserPool(ctx: Vitest): ProcessPool { const providers = new Set() - const executeTests = async (method: 'run' | 'collect', project: WorkspaceProject, files: string[]) => { + const executeTests = async (method: 'run' | 'collect', project: TestProject, files: string[]) => { ctx.state.clearFiles(project, files) const browser = project.browser! @@ -113,11 +113,11 @@ export function createBrowserPool(ctx: Vitest): ProcessPool { await Promise.all(promises) } - const runWorkspaceTests = async (method: 'run' | 'collect', specs: WorkspaceSpec[]) => { - const groupedFiles = new Map() - for (const [project, file] of specs) { + const runWorkspaceTests = async (method: 'run' | 'collect', specs: TestSpecification[]) => { + const groupedFiles = new Map() + for (const { project, moduleId } of specs) { const files = groupedFiles.get(project) || [] - files.push(file) + files.push(moduleId) groupedFiles.set(project, files) } @@ -131,7 +131,7 @@ export function createBrowserPool(ctx: Vitest): ProcessPool { if (isCancelled) { break } - await project.initBrowserProvider() + await project._initBrowserProvider() await executeTests(method, project, files) } @@ -142,7 +142,7 @@ export function createBrowserPool(ctx: Vitest): ProcessPool { ? nodeos.availableParallelism() : nodeos.cpus().length - function getThreadsCount(project: WorkspaceProject) { + function getThreadsCount(project: TestProject) { const config = project.config.browser if (!config.headless || !project.browser!.provider.supportsParallelism) { return 1 diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index f10eb087b8a0..d39733d27622 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -9,7 +9,7 @@ import type { TestSpecification } from './spec' import type { ResolvedConfig, UserConfig, VitestRunMode } from './types/config' import type { CoverageProvider } from './types/coverage' import type { Reporter } from './types/reporter' -import { existsSync, promises as fs } from 'node:fs' +import { existsSync, promises as fs, readFileSync } from 'node:fs' import { getTasks, hasFailed } from '@vitest/runner/utils' import { SnapshotManager } from '@vitest/snapshot/manager' import { noop, slash, toArray } from '@vitest/utils' @@ -28,11 +28,11 @@ import { resolveConfig } from './config/resolveConfig' import { FilesNotFoundError, GitNotFoundError } from './errors' import { Logger } from './logger' import { VitestPackageInstaller } from './packageInstaller' -import { createPool, getFilePoolName } from './pool' +import { createPool } from './pool' +import { TestProject } from './project' import { BlobReporter, readBlobs } from './reporters/blob' import { createBenchmarkReporters, createReporters } from './reporters/utils' import { StateManager } from './state' -import { WorkspaceProject } from './workspace' import { resolveWorkspace } from './workspace/resolveWorkspace' const WATCHER_DEBOUNCE = 100 @@ -75,15 +75,15 @@ export class Vitest { public packageInstaller: VitestPackageInstaller - private coreWorkspaceProject!: WorkspaceProject + private coreWorkspaceProject!: TestProject /** @private */ - public resolvedProjects: WorkspaceProject[] = [] - public projects: WorkspaceProject[] = [] + public resolvedProjects: TestProject[] = [] + public projects: TestProject[] = [] public distPath = distDir - private _cachedSpecs = new Map() + private _cachedSpecs = new Map() private _workspaceConfigPath?: string /** @deprecated use `_cachedSpecs` */ @@ -193,7 +193,7 @@ export class Vitest { ) } if (!this.coreWorkspaceProject) { - this.coreWorkspaceProject = WorkspaceProject.createBasicProject(this) + this.coreWorkspaceProject = TestProject.createBasicProject(this) } if (this.config.testNamePattern) { @@ -217,19 +217,19 @@ export class Vitest { /** * @internal */ - async _createCoreProject() { - this.coreWorkspaceProject = await WorkspaceProject.createCoreProject(this) + _createCoreProject() { + this.coreWorkspaceProject = TestProject.createBasicProject(this) return this.coreWorkspaceProject } - public getCoreWorkspaceProject(): WorkspaceProject { + public getCoreWorkspaceProject(): TestProject { return this.coreWorkspaceProject } /** * @deprecated use Reported Task API instead */ - public getProjectByTaskId(taskId: string): WorkspaceProject { + public getProjectByTaskId(taskId: string): TestProject { const task = this.state.idMap.get(taskId) const projectName = (task as File).projectName || task?.file?.projectName || '' return this.projects.find(p => p.getName() === projectName) @@ -271,7 +271,7 @@ export class Vitest { this._workspaceConfigPath = workspaceConfigPath if (!workspaceConfigPath) { - return [await this._createCoreProject()] + return [this._createCoreProject()] } const workspaceModule = await this.runner.executeFile(workspaceConfigPath) as { @@ -315,7 +315,7 @@ export class Vitest { await this.report('onInit', this) await this.report('onPathsCollected', files.flatMap(f => f.filepath)) - const workspaceSpecs = new Map() + const workspaceSpecs = new Map() for (const file of files) { const project = this.getProjectByName(file.projectName) const specs = workspaceSpecs.get(project) || [] @@ -454,7 +454,7 @@ export class Vitest { } private async getTestDependencies(spec: WorkspaceSpec, deps = new Set()) { - const addImports = async (project: WorkspaceProject, filepath: string) => { + const addImports = async (project: TestProject, filepath: string) => { if (deps.has(filepath)) { return } @@ -534,7 +534,7 @@ export class Vitest { * @deprecated remove when vscode extension supports "getFileWorkspaceSpecs" */ getProjectsByTestFile(file: string) { - return this.getFileWorkspaceSpecs(file) + return this.getFileWorkspaceSpecs(file) as WorkspaceSpec[] } getFileWorkspaceSpecs(file: string) { @@ -543,14 +543,13 @@ export class Vitest { return _cached } - const specs: WorkspaceSpec[] = [] + const specs: TestSpecification[] = [] for (const project of this.projects) { if (project.isTestFile(file)) { - const pool = getFilePoolName(project, file) - specs.push(project.createSpec(file, pool)) + specs.push(project.createSpecification(file)) } if (project.isTypecheckFile(file)) { - specs.push(project.createSpec(file, 'typescript')) + specs.push(project.createSpecification(file, 'typescript')) } } specs.forEach(spec => this.ensureSpecCached(spec)) @@ -558,13 +557,13 @@ export class Vitest { } async initializeGlobalSetup(paths: TestSpecification[]) { - const projects = new Set(paths.map(spec => spec.project.workspaceProject)) + const projects = new Set(paths.map(spec => spec.project)) const coreProject = this.getCoreWorkspaceProject() if (!projects.has(coreProject)) { projects.add(coreProject) } for (const project of projects) { - await project.initializeGlobalSetup() + await project._initializeGlobalSetup() } } @@ -688,7 +687,7 @@ export class Vitest { } async initBrowserServers() { - await Promise.all(this.projects.map(p => p.initBrowserServer())) + await Promise.all(this.projects.map(p => p._initBrowserServer())) } async rerunFiles(files: string[] = this.state.getFilepaths(), trigger?: string, allTestsRun = true) { @@ -889,14 +888,15 @@ export class Vitest { onAdd = async (id: string) => { id = slash(id) this.updateLastChanged(id) + const fileContent = readFileSync(id, 'utf-8') - const matchingProjects: WorkspaceProject[] = [] - await Promise.all(this.projects.map(async (project) => { - if (await project.isTargetFile(id)) { + const matchingProjects: TestProject[] = [] + this.projects.forEach((project) => { + if (project.matchesTestGlob(id, fileContent)) { matchingProjects.push(project) - project.testFilesList?.push(id) + project._markTestFile(id) } - })) + }) if (matchingProjects.length > 0) { this.changedTests.add(id) @@ -1029,10 +1029,10 @@ export class Vitest { } // do teardown before closing the server for await (const project of teardownProjects.reverse()) { - await project.teardownGlobalSetup() + await project._teardownGlobalSetup() } - const closePromises: unknown[] = this.resolvedProjects.map(w => w.close().then(() => w.server = undefined as any)) + const closePromises: unknown[] = this.resolvedProjects.map(w => w.close().then(() => (w as any)._vite = undefined as any)) // close the core workspace server only once // it's possible that it's not initialized at all because it's not running any tests if (!this.resolvedProjects.includes(this.coreWorkspaceProject)) { @@ -1107,22 +1107,21 @@ export class Vitest { } public async globTestSpecs(filters: string[] = []) { - const files: WorkspaceSpec[] = [] + const files: TestSpecification[] = [] await Promise.all(this.projects.map(async (project) => { const { testFiles, typecheckTestFiles } = await project.globTestFiles(filters) testFiles.forEach((file) => { - const pool = getFilePoolName(project, file) - const spec = project.createSpec(file, pool) + const spec = project.createSpecification(file) this.ensureSpecCached(spec) files.push(spec) }) typecheckTestFiles.forEach((file) => { - const spec = project.createSpec(file, 'typescript') + const spec = project.createSpecification(file, 'typescript') this.ensureSpecCached(spec) files.push(spec) }) })) - return files + return files as WorkspaceSpec[] } /** @@ -1132,7 +1131,7 @@ export class Vitest { return this.globTestSpecs(filters) } - private ensureSpecCached(spec: WorkspaceSpec) { + private ensureSpecCached(spec: TestSpecification) { const file = spec[1] const specs = this._cachedSpecs.get(file) || [] const included = specs.some(_s => _s[0] === spec[0] && _s[2].pool === spec[2].pool) diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index 54f928df8d43..44de1ea400db 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -1,7 +1,7 @@ import type { ErrorWithDiff, ParsedStack } from '@vitest/utils' import type { Vitest } from './core' import type { ErrorOptions } from './logger' -import type { WorkspaceProject } from './workspace' +import type { TestProject } from './project' /* eslint-disable prefer-template */ import { existsSync, readFileSync } from 'node:fs' import { Writable } from 'node:stream' @@ -55,7 +55,7 @@ export function capturePrintError( export function printError( error: unknown, - project: WorkspaceProject | undefined, + project: TestProject | undefined, options: PrintErrorOptions, ): PrintErrorResult | undefined { const { showCodeFrame = true, type, printProperties = true } = options @@ -338,7 +338,7 @@ function printErrorMessage(error: ErrorWithDiff, logger: Logger) { export function printStack( logger: Logger, - project: WorkspaceProject, + project: TestProject, stack: ParsedStack[], highlight: ParsedStack | undefined, errorProperties: Record, diff --git a/packages/vitest/src/node/globalSetup.ts b/packages/vitest/src/node/globalSetup.ts index 33d17d8aadfe..295b8a862035 100644 --- a/packages/vitest/src/node/globalSetup.ts +++ b/packages/vitest/src/node/globalSetup.ts @@ -4,11 +4,20 @@ import type { ResolvedConfig } from './types/config' import { toArray } from '@vitest/utils' export interface GlobalSetupContext { + /** + * Config of the current project. + */ config: ResolvedConfig + /** + * Provide a value to the test context. This value will be available to all tests via `inject`. + */ provide: ( key: T, value: ProvidedContext[T] ) => void + /** + * Register a function that will be called before tests run again in watch mode. + */ onTestsRerun: (cb: OnTestsRerunHandler) => void } diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index 5254bf6b3d4e..40f9ea9f4273 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -4,7 +4,7 @@ import type { Writable } from 'node:stream' import type { TypeCheckError } from '../typecheck/typechecker' import type { Vitest } from './core' import type { PrintErrorResult } from './error' -import type { WorkspaceProject } from './workspace' +import type { TestProject } from './project' import { Console } from 'node:console' import { toArray } from '@vitest/utils' import { parseErrorStacktrace } from '@vitest/utils/source-map' @@ -18,7 +18,7 @@ import { RandomSequencer } from './sequencers/RandomSequencer' export interface ErrorOptions { type?: string fullStack?: boolean - project?: WorkspaceProject + project?: TestProject verbose?: boolean screenshotPaths?: string[] task?: Task @@ -245,7 +245,7 @@ export class Logger { } } - printBrowserBanner(project: WorkspaceProject) { + printBrowserBanner(project: TestProject) { if (!project.browser) { return } diff --git a/packages/vitest/src/node/plugins/workspace.ts b/packages/vitest/src/node/plugins/workspace.ts index 8f4e3c9027e0..f9c066cc1020 100644 --- a/packages/vitest/src/node/plugins/workspace.ts +++ b/packages/vitest/src/node/plugins/workspace.ts @@ -1,6 +1,6 @@ import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite' +import type { TestProject } from '../project' import type { ResolvedConfig, UserWorkspaceConfig } from '../types/config' -import type { WorkspaceProject } from '../workspace' import { existsSync, readFileSync } from 'node:fs' import { deepMerge } from '@vitest/utils' import { basename, dirname, relative, resolve } from 'pathe' @@ -26,7 +26,7 @@ interface WorkspaceOptions extends UserWorkspaceConfig { } export function WorkspaceVitestPlugin( - project: WorkspaceProject, + project: TestProject, options: WorkspaceOptions, ) { return [ @@ -153,7 +153,7 @@ export function WorkspaceVitestPlugin( }, async configureServer(server) { const options = deepMerge({}, configDefaults, server.config.test || {}) - await project.setServer(options, server) + await project._configureServer(options, server) await server.watcher.close() }, diff --git a/packages/vitest/src/node/pool.ts b/packages/vitest/src/node/pool.ts index eef580e696d5..ef11f6167966 100644 --- a/packages/vitest/src/node/pool.ts +++ b/packages/vitest/src/node/pool.ts @@ -1,8 +1,8 @@ import type { Awaitable } from '@vitest/utils' import type { Vitest } from './core' +import type { TestProject } from './project' import type { TestSpecification } from './spec' import type { BuiltinPool, Pool } from './types/pool-options' -import type { WorkspaceProject } from './workspace' import mm from 'micromatch' import { isWindows } from '../utils/env' import { createForksPool } from './pools/forks' @@ -18,7 +18,7 @@ export type WorkspaceSpec = TestSpecification & [ /** * @deprecated use spec.project instead */ - project: WorkspaceProject, + project: TestProject, /** * @deprecated use spec.moduleId instead */ @@ -57,14 +57,14 @@ export const builtinPools: BuiltinPool[] = [ 'typescript', ] -function getDefaultPoolName(project: WorkspaceProject): Pool { +function getDefaultPoolName(project: TestProject): Pool { if (project.config.browser.enabled) { return 'browser' } return project.config.pool } -export function getFilePoolName(project: WorkspaceProject, file: string) { +export function getFilePoolName(project: TestProject, file: string) { for (const [glob, pool] of project.config.poolMatchGlobs) { if ((pool as Pool) === 'browser') { throw new Error( diff --git a/packages/vitest/src/node/pools/forks.ts b/packages/vitest/src/node/pools/forks.ts index cf925eb421fb..4b4c015c4eb4 100644 --- a/packages/vitest/src/node/pools/forks.ts +++ b/packages/vitest/src/node/pools/forks.ts @@ -3,8 +3,8 @@ import type { RunnerRPC, RuntimeRPC } from '../../types/rpc' import type { ContextRPC, ContextTestEnvironment } from '../../types/worker' import type { Vitest } from '../core' import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool' +import type { TestProject } from '../project' import type { SerializedConfig } from '../types/config' -import type { WorkspaceProject } from '../workspace' import EventEmitter from 'node:events' import * as nodeos from 'node:os' import { resolve } from 'node:path' @@ -16,7 +16,7 @@ import { wrapSerializableConfig } from '../../utils/config-helpers' import { envsOrder, groupFilesByEnv } from '../../utils/test-helpers' import { createMethodsRPC } from './rpc' -function createChildProcessChannel(project: WorkspaceProject) { +function createChildProcessChannel(project: TestProject) { const emitter = new EventEmitter() const cleanup = () => emitter.removeAllListeners() @@ -99,7 +99,7 @@ export function createForksPool( let id = 0 async function runFiles( - project: WorkspaceProject, + project: TestProject, config: SerializedConfig, files: string[], environment: ContextTestEnvironment, @@ -153,8 +153,8 @@ export function createForksPool( // Cancel pending tasks from pool when possible ctx.onCancel(() => pool.cancelPendingTasks()) - const configs = new WeakMap() - const getConfig = (project: WorkspaceProject): SerializedConfig => { + const configs = new WeakMap() + const getConfig = (project: TestProject): SerializedConfig => { if (configs.has(project)) { return configs.get(project)! } diff --git a/packages/vitest/src/node/pools/rpc.ts b/packages/vitest/src/node/pools/rpc.ts index 28b65a4bcd97..43694e3a24aa 100644 --- a/packages/vitest/src/node/pools/rpc.ts +++ b/packages/vitest/src/node/pools/rpc.ts @@ -1,7 +1,7 @@ import type { RawSourceMap } from 'vite-node' import type { RuntimeRPC } from '../../types/rpc' +import type { TestProject } from '../project' import type { ResolveSnapshotPathHandlerContext } from '../types/config' -import type { WorkspaceProject } from '../workspace' import { mkdir, writeFile } from 'node:fs/promises' import { join } from 'pathe' import { hash } from '../hash' @@ -13,7 +13,7 @@ interface MethodsOptions { cacheFs?: boolean } -export function createMethodsRPC(project: WorkspaceProject, options: MethodsOptions = {}): RuntimeRPC { +export function createMethodsRPC(project: TestProject, options: MethodsOptions = {}): RuntimeRPC { const ctx = project.ctx const cacheFs = options.cacheFs ?? false return { diff --git a/packages/vitest/src/node/pools/threads.ts b/packages/vitest/src/node/pools/threads.ts index 4d443975057a..bfb224a6560a 100644 --- a/packages/vitest/src/node/pools/threads.ts +++ b/packages/vitest/src/node/pools/threads.ts @@ -3,9 +3,9 @@ import type { RunnerRPC, RuntimeRPC } from '../../types/rpc' import type { ContextTestEnvironment } from '../../types/worker' import type { Vitest } from '../core' import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool' +import type { TestProject } from '../project' import type { SerializedConfig } from '../types/config' import type { WorkerContext } from '../types/worker' -import type { WorkspaceProject } from '../workspace' import * as nodeos from 'node:os' import { resolve } from 'node:path' import { MessageChannel } from 'node:worker_threads' @@ -15,7 +15,7 @@ import { groupBy } from '../../utils/base' import { envsOrder, groupFilesByEnv } from '../../utils/test-helpers' import { createMethodsRPC } from './rpc' -function createWorkerChannel(project: WorkspaceProject) { +function createWorkerChannel(project: TestProject) { const channel = new MessageChannel() const port = channel.port2 const workerPort = channel.port1 @@ -93,7 +93,7 @@ export function createThreadsPool( let id = 0 async function runFiles( - project: WorkspaceProject, + project: TestProject, config: SerializedConfig, files: string[], environment: ContextTestEnvironment, @@ -151,8 +151,8 @@ export function createThreadsPool( // Cancel pending tasks from pool when possible ctx.onCancel(() => pool.cancelPendingTasks()) - const configs = new WeakMap() - const getConfig = (project: WorkspaceProject): SerializedConfig => { + const configs = new WeakMap() + const getConfig = (project: TestProject): SerializedConfig => { if (configs.has(project)) { return configs.get(project)! } diff --git a/packages/vitest/src/node/pools/typecheck.ts b/packages/vitest/src/node/pools/typecheck.ts index 805e28b92695..e7efac948919 100644 --- a/packages/vitest/src/node/pools/typecheck.ts +++ b/packages/vitest/src/node/pools/typecheck.ts @@ -2,18 +2,18 @@ import type { DeferPromise } from '@vitest/utils' import type { TypecheckResults } from '../../typecheck/typechecker' import type { Vitest } from '../core' import type { ProcessPool, WorkspaceSpec } from '../pool' -import type { WorkspaceProject } from '../workspace' +import type { TestProject } from '../project' import { hasFailed } from '@vitest/runner/utils' import { createDefer } from '@vitest/utils' import { Typechecker } from '../../typecheck/typechecker' import { groupBy } from '../../utils/base' export function createTypecheckPool(ctx: Vitest): ProcessPool { - const promisesMap = new WeakMap>() - const rerunTriggered = new WeakSet() + const promisesMap = new WeakMap>() + const rerunTriggered = new WeakSet() async function onParseEnd( - project: WorkspaceProject, + project: TestProject, { files, sourceErrors }: TypecheckResults, ) { const checker = project.typechecker! @@ -49,7 +49,7 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool { } async function createWorkspaceTypechecker( - project: WorkspaceProject, + project: TestProject, files: string[], ) { const checker = project.typechecker ?? new Typechecker(project) @@ -90,7 +90,7 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool { return checker } - async function startTypechecker(project: WorkspaceProject, files: string[]) { + async function startTypechecker(project: TestProject, files: string[]) { if (project.typechecker) { return project.typechecker } diff --git a/packages/vitest/src/node/pools/vmForks.ts b/packages/vitest/src/node/pools/vmForks.ts index 14cd5881a35f..554b16c69989 100644 --- a/packages/vitest/src/node/pools/vmForks.ts +++ b/packages/vitest/src/node/pools/vmForks.ts @@ -3,8 +3,8 @@ import type { RunnerRPC, RuntimeRPC } from '../../types/rpc' import type { ContextRPC, ContextTestEnvironment } from '../../types/worker' import type { Vitest } from '../core' import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool' +import type { TestProject } from '../project' import type { ResolvedConfig, SerializedConfig } from '../types/config' -import type { WorkspaceProject } from '../workspace' import EventEmitter from 'node:events' import * as nodeos from 'node:os' import { resolve } from 'node:path' @@ -19,7 +19,7 @@ import { createMethodsRPC } from './rpc' const suppressWarningsPath = resolve(rootDir, './suppress-warnings.cjs') -function createChildProcessChannel(project: WorkspaceProject) { +function createChildProcessChannel(project: TestProject) { const emitter = new EventEmitter() const cleanup = () => emitter.removeAllListeners() @@ -107,7 +107,7 @@ export function createVmForksPool( let id = 0 async function runFiles( - project: WorkspaceProject, + project: TestProject, config: SerializedConfig, files: string[], environment: ContextTestEnvironment, @@ -161,8 +161,8 @@ export function createVmForksPool( // Cancel pending tasks from pool when possible ctx.onCancel(() => pool.cancelPendingTasks()) - const configs = new Map() - const getConfig = (project: WorkspaceProject): SerializedConfig => { + const configs = new Map() + const getConfig = (project: TestProject): SerializedConfig => { if (configs.has(project)) { return configs.get(project)! } diff --git a/packages/vitest/src/node/pools/vmThreads.ts b/packages/vitest/src/node/pools/vmThreads.ts index 01420cfc87be..4b5b72670403 100644 --- a/packages/vitest/src/node/pools/vmThreads.ts +++ b/packages/vitest/src/node/pools/vmThreads.ts @@ -3,9 +3,9 @@ import type { RunnerRPC, RuntimeRPC } from '../../types/rpc' import type { ContextTestEnvironment } from '../../types/worker' import type { Vitest } from '../core' import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool' +import type { TestProject } from '../project' import type { ResolvedConfig, SerializedConfig } from '../types/config' import type { WorkerContext } from '../types/worker' -import type { WorkspaceProject } from '../workspace' import * as nodeos from 'node:os' import { resolve } from 'node:path' import { MessageChannel } from 'node:worker_threads' @@ -18,7 +18,7 @@ import { createMethodsRPC } from './rpc' const suppressWarningsPath = resolve(rootDir, './suppress-warnings.cjs') -function createWorkerChannel(project: WorkspaceProject) { +function createWorkerChannel(project: TestProject) { const channel = new MessageChannel() const port = channel.port2 const workerPort = channel.port1 @@ -98,7 +98,7 @@ export function createVmThreadsPool( let id = 0 async function runFiles( - project: WorkspaceProject, + project: TestProject, config: SerializedConfig, files: string[], environment: ContextTestEnvironment, @@ -156,8 +156,8 @@ export function createVmThreadsPool( // Cancel pending tasks from pool when possible ctx.onCancel(() => pool.cancelPendingTasks()) - const configs = new Map() - const getConfig = (project: WorkspaceProject): SerializedConfig => { + const configs = new Map() + const getConfig = (project: TestProject): SerializedConfig => { if (configs.has(project)) { return configs.get(project)! } diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/project.ts similarity index 56% rename from packages/vitest/src/node/workspace.ts rename to packages/vitest/src/node/project.ts index 0fce02385324..e815bd0925b5 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/project.ts @@ -1,4 +1,5 @@ import type { + ModuleNode, TransformResult, ViteDevServer, InlineConfig as ViteInlineConfig, @@ -7,7 +8,6 @@ import type { Typechecker } from '../typecheck/typechecker' import type { ProvidedContext } from '../types/general' import type { Vitest } from './core' import type { GlobalSetupFile } from './globalSetup' -import type { WorkspaceSpec as DeprecatedWorkspaceSpec } from './pool' import type { BrowserServer } from './types/browser' import type { ResolvedConfig, @@ -15,7 +15,7 @@ import type { UserConfig, UserWorkspaceConfig, } from './types/config' -import { promises as fs } from 'node:fs' +import { promises as fs, readFileSync } from 'node:fs' import { rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import path from 'node:path' @@ -38,95 +38,76 @@ import { loadGlobalSetupFiles } from './globalSetup' import { CoverageTransform } from './plugins/coverageTransform' import { MocksPlugins } from './plugins/mocks' import { WorkspaceVitestPlugin } from './plugins/workspace' -import { TestProject } from './reported-workspace-project' +import { type WorkspaceSpec as DeprecatedWorkspaceSpec, getFilePoolName } from './pool' import { TestSpecification } from './spec' import { createViteServer } from './vite' -interface InitializeProjectOptions extends UserWorkspaceConfig { - workspaceConfigPath: string - extends?: string -} - -export async function initializeProject( - workspacePath: string | number, - ctx: Vitest, - options: InitializeProjectOptions, -) { - const project = new WorkspaceProject(workspacePath, ctx, options) - - const { extends: extendsConfig, workspaceConfigPath, ...restOptions } = options - const root - = options.root - || (typeof workspacePath === 'number' - ? undefined - : workspacePath.endsWith('/') - ? workspacePath - : dirname(workspacePath)) - - const configFile = extendsConfig - ? resolve(dirname(workspaceConfigPath), extendsConfig) - : typeof workspacePath === 'number' || workspacePath.endsWith('/') - ? false - : workspacePath - - const config: ViteInlineConfig = { - ...restOptions, - root, - configFile, - // this will make "mode": "test" | "benchmark" inside defineConfig - mode: options.test?.mode || options.mode || ctx.config.mode, - plugins: [ - ...(options.plugins || []), - WorkspaceVitestPlugin(project, { ...options, root, workspacePath }), - ], - } - - await createViteServer(config) - - return project -} +export class TestProject { + /** + * The global Vitest instance. + * @experimental The public Vitest API is experimental and does not follow semver. + */ + public readonly vitest: Vitest + + /** + * Resolved global configuration. If there are no workspace projects, this will be the same as `config`. + */ + public readonly globalConfig: ResolvedConfig + + /** + * Browser instance if the browser is enabled. This is initialized when the tests run for the first time. + */ + public browser?: BrowserServer + + /** @deprecated use `vitest` instead */ + public ctx: Vitest + + /** + * The workspace project this test project is associated with. + * @deprecated use the current project instead + */ + public readonly workspaceProject: TestProject + /** @deprecated use the current project instead */ + public testProject: TestProject -export class WorkspaceProject { - configOverride: Partial | undefined + public readonly id = nanoid() + public readonly tmpDir = join(tmpdir(), this.id) - config!: ResolvedConfig - server!: ViteDevServer vitenode!: ViteNodeServer runner!: ViteNodeRunner - browser?: BrowserServer typechecker?: Typechecker - closingPromise: Promise | undefined - - testFilesList: string[] | null = null - typecheckFilesList: string[] | null = null + private closingPromise: Promise | undefined - public testProject!: TestProject - - public readonly id = nanoid() - public readonly tmpDir = join(tmpdir(), this.id) + private testFilesList: string[] | null = null + private typecheckFilesList: string[] | null = null - private _globalSetups: GlobalSetupFile[] | undefined + private _globalSetups?: GlobalSetupFile[] private _provided: ProvidedContext = {} as any + private _config?: ResolvedConfig + private _vite?: ViteDevServer constructor( + /** @deprecated */ public path: string | number, - public ctx: Vitest, + vitest: Vitest, + /** @deprecated */ public options?: InitializeProjectOptions, - ) {} - - getName(): string { - return this.config.name || '' - } - - isCore() { - return this.ctx.getCoreWorkspaceProject() === this + ) { + this.testProject = this + this.vitest = vitest + this.ctx = vitest + this.workspaceProject = this + this.globalConfig = vitest.config } + /** + * Provide a value to the test context. This value will be available to all tests with `inject`. + */ provide( key: T, value: ProvidedContext[T], - ) { + ): void { try { structuredClone(value) } @@ -138,9 +119,13 @@ export class WorkspaceProject { }, ) } + // casting `any` because the devault type is `never` since `ProvidedContext` is empty (this._provided as any)[key] = value } + /** + * Get the provided context. The project context is merged with the global context. + */ getProvidedContext(): ProvidedContext { if (this.isCore()) { return this._provided @@ -148,16 +133,89 @@ export class WorkspaceProject { // globalSetup can run even if core workspace is not part of the test run // so we need to inherit its provided context return { - ...this.ctx.getCoreWorkspaceProject().getProvidedContext(), + ...this.vitest.getCoreWorkspaceProject().getProvidedContext(), ...this._provided, } } + /** + * Creates a new test specification. Specifications describe how to run tests. + * @param moduleId The file path + */ + public createSpecification(moduleId: string, pool?: string): TestSpecification { + return new TestSpecification( + this, + moduleId, + pool || getFilePoolName(this, moduleId), + ) + } + + public toJSON(): SerializedTestProject { + return { + name: this.name, + serializedConfig: this.serializedConfig, + context: this.getProvidedContext(), + } + } + + /** + * Vite's dev server instance. Every workspace project has its own server. + */ + public get vite(): ViteDevServer { + if (!this._vite) { + throw new Error('The server was not set. It means that `project.vite` was called before the Vite server was established.') + } + return this._vite + } + + /** + * Resolved project configuration. + */ + public get config(): ResolvedConfig { + if (!this._config) { + throw new Error('The config was not set. It means that `project.config` was called before the Vite server was established.') + } + return this._config + } + + /** + * The name of the project or an empty string if not set. + */ + public get name(): string { + return this.config.name || '' + } + + /** + * Serialized project configuration. This is the config that tests receive. + */ + public get serializedConfig(): SerializedConfig { + return this._serializeOverridenConfig() + } + + /** @deprecated use `vite` instead */ + public get server(): ViteDevServer { + return this._vite! + } + + /** + * Is this the root project. The root project is the one that has the root config. + */ + public isCore(): boolean { + return this.vitest.getCoreWorkspaceProject() === this + } + + /** @deprecated use createSpecification instead */ public createSpec(moduleId: string, pool: string): DeprecatedWorkspaceSpec { return new TestSpecification(this, moduleId, pool) as DeprecatedWorkspaceSpec } - async initializeGlobalSetup() { + /** @deprecated */ + initializeGlobalSetup() { + return this._initializeGlobalSetup() + } + + /** @private */ + async _initializeGlobalSetup() { if (this._globalSetups) { return } @@ -171,7 +229,7 @@ export class WorkspaceProject { const teardown = await globalSetupFile.setup?.({ provide: (key, value) => this.provide(key, value), config: this.config, - onTestsRerun: cb => this.ctx.onTestsRerun(cb), + onTestsRerun: cb => this.vitest.onTestsRerun(cb), }) if (teardown == null || !!globalSetupFile.teardown) { continue @@ -185,7 +243,13 @@ export class WorkspaceProject { } } - async teardownGlobalSetup() { + /** @deprecated */ + teardownGlobalSetup() { + return this._teardownGlobalSetup() + } + + /** @private */ + async _teardownGlobalSetup() { if (!this._globalSetups) { return } @@ -194,35 +258,53 @@ export class WorkspaceProject { } } + /** @deprecated use `vitest.logger` instead */ get logger() { - return this.ctx.logger + return this.vitest.logger } // it's possible that file path was imported with different queries (?raw, ?url, etc) - getModulesByFilepath(file: string) { + /** @deprecated use `.vite` or `.browser.vite` directly */ + getModulesByFilepath(file: string): Set { const set = this.server.moduleGraph.getModulesByFile(file) || this.browser?.vite.moduleGraph.getModulesByFile(file) return set || new Set() } - getModuleById(id: string) { + /** @deprecated use `.vite` or `.browser.vite` directly */ + getModuleById(id: string): ModuleNode | undefined { return ( this.server.moduleGraph.getModuleById(id) || this.browser?.vite.moduleGraph.getModuleById(id) ) } + /** @deprecated use `.vite` or `.browser.vite` directly */ getSourceMapModuleById(id: string): TransformResult['map'] | undefined { const mod = this.server.moduleGraph.getModuleById(id) return mod?.ssrTransformResult?.map || mod?.transformResult?.map } + /** @deprecated use `vitest.reporters` instead */ get reporters() { return this.ctx.reporters } - async globTestFiles(filters: string[] = []) { + /** + * Get all files in the project that match the globs in the config and the filters. + * @param filters String filters to match the test files. + */ + async globTestFiles(filters: string[] = []): Promise<{ + /** + * Test files that match the filters. + */ + testFiles: string[] + /** + * Typecheck test files that match the filters. This will be empty unless `typecheck.enabled` is `true`. + */ + typecheckTestFiles: string[] + }> { const dir = this.config.dir || this.config.root const { include, exclude, includeSource } = this.config @@ -253,12 +335,12 @@ export class WorkspaceProject { } } - async globAllTestFiles( + private async globAllTestFiles( include: string[], exclude: string[], includeSource: string[] | undefined, cwd: string, - ) { + ): Promise { if (this.testFilesList) { return this.testFilesList } @@ -272,7 +354,7 @@ export class WorkspaceProject { files.map(async (file) => { try { const code = await fs.readFile(file, 'utf-8') - if (this.isInSourceTestFile(code)) { + if (this.isInSourceTestCode(code)) { testFiles.push(file) } } @@ -288,14 +370,37 @@ export class WorkspaceProject { return testFiles } - isTestFile(id: string) { - return this.testFilesList && this.testFilesList.includes(id) + isBrowserEnabled(): boolean { + return isBrowserEnabled(this.config) + } + + /** @private */ + _markTestFile(testPath: string): void { + this.testFilesList?.push(testPath) + } + + /** + * Returns if the file is a test file. Requires `.globTestFiles()` to be called first. + * @private + */ + isTestFile(testPath: string): boolean { + return !!this.testFilesList && this.testFilesList.includes(testPath) + } + + /** + * Returns if the file is a typecheck test file. Requires `.globTestFiles()` to be called first. + * @private + */ + isTypecheckFile(testPath: string): boolean { + return !!this.typecheckFilesList && this.typecheckFilesList.includes(testPath) } - isTypecheckFile(id: string) { - return this.typecheckFilesList && this.typecheckFilesList.includes(id) + /** @deprecated use `serializedConfig` instead */ + getSerializableConfig(): SerializedConfig { + return this._serializeOverridenConfig() } + /** @private */ async globFiles(include: string[], exclude: string[], cwd: string) { const globOptions: fg.Options = { dot: true, @@ -310,8 +415,11 @@ export class WorkspaceProject { return files.map(file => slash(path.resolve(cwd, file))) } - async isTargetFile(id: string, source?: string): Promise { - const relativeId = relative(this.config.dir || this.config.root, id) + /** + * Test if a file matches the test globs. This does the actual glob matching unlike `isTestFile`. + */ + public matchesTestGlob(filepath: string, source?: string): boolean { + const relativeId = relative(this.config.dir || this.config.root, filepath) if (mm.isMatch(relativeId, this.config.exclude)) { return false } @@ -322,17 +430,22 @@ export class WorkspaceProject { this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource) ) { - source = source || (await fs.readFile(id, 'utf-8')) - return this.isInSourceTestFile(source) + const code = source || readFileSync(filepath, 'utf-8') + return this.isInSourceTestCode(code) } return false } - isInSourceTestFile(code: string) { + /** @deprecated use `matchesTestGlob` instead */ + async isTargetFile(id: string, source?: string): Promise { + return this.matchesTestGlob(id, source) + } + + private isInSourceTestCode(code: string): boolean { return code.includes('import.meta.vitest') } - filterFiles(testFiles: string[], filters: string[], dir: string) { + private filterFiles(testFiles: string[], filters: string[], dir: string): string[] { if (filters.length && process.platform === 'win32') { filters = filters.map(f => slash(f)) } @@ -360,15 +473,16 @@ export class WorkspaceProject { return testFiles } - async initBrowserServer() { + /** @private */ + async _initBrowserServer() { if (!this.isBrowserEnabled() || this.browser) { return } - await this.ctx.packageInstaller.ensureInstalled('@vitest/browser', this.config.root, this.ctx.version) + await this.vitest.packageInstaller.ensureInstalled('@vitest/browser', this.config.root, this.ctx.version) const { createBrowserServer, distRoot } = await import('@vitest/browser') const browser = await createBrowserServer( this, - this.server.config.configFile, + this.vite.config.configFile, [ ...MocksPlugins({ filter(id) { @@ -383,44 +497,48 @@ export class WorkspaceProject { ) this.browser = browser if (this.config.browser.ui) { - setup(this.ctx, browser.vite) + setup(this.vitest, browser.vite) } } - static createBasicProject(ctx: Vitest) { - const project = new WorkspaceProject( - ctx.config.name || ctx.config.root, - ctx, - ) - project.vitenode = ctx.vitenode - project.server = ctx.server - project.runner = ctx.runner - project.config = ctx.config - for (const _providedKey in ctx.config.provide) { - const providedKey = _providedKey as keyof ProvidedContext - // type is very strict here, so we cast it to any - (project.provide as (key: string, value: unknown) => void)( - providedKey, - ctx.config.provide[providedKey], - ) + /** + * Closes the project and all associated resources. This will only be called once. + * If the resources are needed again, create a new project. + */ + public close(): Promise { + if (!this.closingPromise) { + this.closingPromise = Promise.all( + [ + this.vite?.close(), + this.typechecker?.stop(), + this.browser?.close(), + this.clearTmpDir(), + ].filter(Boolean), + ).then(() => (this._provided = {} as any)) } - project.testProject = new TestProject(project) - return project + return this.closingPromise + } + + /** @deprecated use `name` instead */ + public getName(): string { + return this.config.name || '' } - static async createCoreProject(ctx: Vitest) { - return WorkspaceProject.createBasicProject(ctx) + /** @deprecated internal */ + public setServer(options: UserConfig, server: ViteDevServer) { + return this._configureServer(options, server) } - async setServer(options: UserConfig, server: ViteDevServer) { - this.config = resolveConfig( - this.ctx.mode, + /** @private */ + async _configureServer(options: UserConfig, server: ViteDevServer): Promise { + this._config = resolveConfig( + this.vitest.mode, { ...options, - coverage: this.ctx.config.coverage, + coverage: this.vitest.config.coverage, }, server.config, - this.ctx.logger, + this.vitest.logger, ) for (const _providedKey in this.config.provide) { const providedKey = _providedKey as keyof ProvidedContext @@ -432,9 +550,9 @@ export class WorkspaceProject { } this.closingPromise = undefined - this.testProject = new TestProject(this) + this.testProject = this - this.server = server + this._vite = server this.vitenode = new ViteNodeServer(server, this.config.server) const node = this.vitenode @@ -450,54 +568,123 @@ export class WorkspaceProject { }) } - isBrowserEnabled(): boolean { - return isBrowserEnabled(this.config) - } - - getSerializableConfig(): SerializedConfig { + private _serializeOverridenConfig(): SerializedConfig { // TODO: serialize the config _once_ or when needed const config = serializeConfig( this.config, - this.ctx.config, - this.server.config, + this.vitest.config, + this.vite.config, ) - if (!this.ctx.configOverride) { + if (!this.vitest.configOverride) { return config } return deepMerge( config, - this.ctx.configOverride, + this.vitest.configOverride, ) } - close() { - if (!this.closingPromise) { - this.closingPromise = Promise.all( - [ - this.server?.close(), - this.typechecker?.stop(), - this.browser?.close(), - this.clearTmpDir(), - ].filter(Boolean), - ).then(() => (this._provided = {} as any)) - } - return this.closingPromise - } - - private async clearTmpDir() { + private async clearTmpDir(): Promise { try { await rm(this.tmpDir, { recursive: true }) } catch {} } - async initBrowserProvider() { + /** @deprecated */ + public initBrowserProvider(): Promise { + return this._initBrowserProvider() + } + + /** @private */ + async _initBrowserProvider(): Promise { if (!this.isBrowserEnabled() || this.browser?.provider) { return } if (!this.browser) { - await this.initBrowserServer() + await this._initBrowserServer() } await this.browser?.initBrowserProvider() } + + static createBasicProject(vitest: Vitest): TestProject { + const project = new TestProject( + vitest.config.name || vitest.config.root, + vitest, + ) + project.vitenode = vitest.vitenode + project.runner = vitest.runner + project._vite = vitest.server + project._config = vitest.config + for (const _providedKey in vitest.config.provide) { + const providedKey = _providedKey as keyof ProvidedContext + // type is very strict here, so we cast it to any + (project.provide as (key: string, value: unknown) => void)( + providedKey, + vitest.config.provide[providedKey], + ) + } + project.testProject = project + return project + } + + /** @deprecated */ + static async createCoreProject(vitest: Vitest): Promise { + return TestProject.createBasicProject(vitest) + } +} + +export { + /** @deprecated use `TestProject` instead */ + TestProject as WorkspaceProject, +} + +export interface SerializedTestProject { + name: string + serializedConfig: SerializedConfig + context: ProvidedContext +} + +interface InitializeProjectOptions extends UserWorkspaceConfig { + workspaceConfigPath: string + extends?: string +} + +export async function initializeProject( + workspacePath: string | number, + ctx: Vitest, + options: InitializeProjectOptions, +) { + const project = new TestProject(workspacePath, ctx, options) + + const { extends: extendsConfig, workspaceConfigPath, ...restOptions } = options + const root + = options.root + || (typeof workspacePath === 'number' + ? undefined + : workspacePath.endsWith('/') + ? workspacePath + : dirname(workspacePath)) + + const configFile = extendsConfig + ? resolve(dirname(workspaceConfigPath), extendsConfig) + : typeof workspacePath === 'number' || workspacePath.endsWith('/') + ? false + : workspacePath + + const config: ViteInlineConfig = { + ...restOptions, + root, + configFile, + // this will make "mode": "test" | "benchmark" inside defineConfig + mode: options.test?.mode || options.mode || ctx.config.mode, + plugins: [ + ...(options.plugins || []), + WorkspaceVitestPlugin(project, { ...options, root, workspacePath }), + ], + } + + await createViteServer(config) + + return project } diff --git a/packages/vitest/src/node/reported-workspace-project.ts b/packages/vitest/src/node/reported-workspace-project.ts deleted file mode 100644 index 06548785df7f..000000000000 --- a/packages/vitest/src/node/reported-workspace-project.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { ViteDevServer } from 'vite' -import type { ProvidedContext } from '../types/general' -import type { Vitest } from './core' -import type { ResolvedConfig, ResolvedProjectConfig, SerializedConfig } from './types/config' -import type { WorkspaceProject } from './workspace' - -export class TestProject { - /** - * The global vitest instance. - * @experimental The public Vitest API is experimental and does not follow semver. - */ - public readonly vitest: Vitest - /** - * The workspace project this test project is associated with. - * @experimental The public Vitest API is experimental and does not follow semver. - */ - public readonly workspaceProject: WorkspaceProject - - /** - * Vite's dev server instance. Every workspace project has its own server. - */ - public readonly vite: ViteDevServer - /** - * Resolved project configuration. - */ - public readonly config: ResolvedProjectConfig - /** - * Resolved global configuration. If there are no workspace projects, this will be the same as `config`. - */ - public readonly globalConfig: ResolvedConfig - - /** - * The name of the project or an empty string if not set. - */ - public readonly name: string - - constructor(workspaceProject: WorkspaceProject) { - this.workspaceProject = workspaceProject - this.vitest = workspaceProject.ctx - this.vite = workspaceProject.server - this.globalConfig = workspaceProject.ctx.config - this.config = workspaceProject.config - this.name = workspaceProject.getName() - } - - /** - * Serialized project configuration. This is the config that tests receive. - */ - public get serializedConfig() { - return this.workspaceProject.getSerializableConfig() - } - - /** - * Custom context provided to the project. - */ - public context(): ProvidedContext { - return this.workspaceProject.getProvidedContext() - } - - /** - * Provide a custom serializable context to the project. This context will be available for tests once they run. - */ - public provide( - key: T, - value: ProvidedContext[T], - ): void { - this.workspaceProject.provide(key, value) - } - - public toJSON(): SerializedTestProject { - return { - name: this.name, - serializedConfig: this.serializedConfig, - context: this.context(), - } - } -} - -export interface SerializedTestProject { - name: string - serializedConfig: SerializedConfig - context: ProvidedContext -} diff --git a/packages/vitest/src/node/reporters/blob.ts b/packages/vitest/src/node/reporters/blob.ts index d98f94bde6d1..0b0d80231b55 100644 --- a/packages/vitest/src/node/reporters/blob.ts +++ b/packages/vitest/src/node/reporters/blob.ts @@ -1,7 +1,7 @@ import type { File } from '@vitest/runner' import type { Vitest } from '../core' +import type { TestProject } from '../project' import type { Reporter } from '../types/reporter' -import type { WorkspaceProject } from '../workspace' import { existsSync } from 'node:fs' import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises' import { parse, stringify } from 'flatted' @@ -79,7 +79,7 @@ export class BlobReporter implements Reporter { export async function readBlobs( currentVersion: string, blobsDirectory: string, - projectsArray: WorkspaceProject[], + projectsArray: TestProject[], ) { // using process.cwd() because --merge-reports can only be used in CLI const resolvedDir = resolve(process.cwd(), blobsDirectory) diff --git a/packages/vitest/src/node/reporters/github-actions.ts b/packages/vitest/src/node/reporters/github-actions.ts index 111786604f59..8f4e5a12d7bd 100644 --- a/packages/vitest/src/node/reporters/github-actions.ts +++ b/packages/vitest/src/node/reporters/github-actions.ts @@ -1,7 +1,7 @@ import type { File } from '@vitest/runner' import type { Vitest } from '../core' +import type { TestProject } from '../project' import type { Reporter } from '../types/reporter' -import type { WorkspaceProject } from '../workspace' import { stripVTControlCharacters } from 'node:util' import { getFullName, getTasks } from '@vitest/runner/utils' import { capturePrintError } from '../error' @@ -16,7 +16,7 @@ export class GithubActionsReporter implements Reporter { onFinished(files: File[] = [], errors: unknown[] = []) { // collect all errors and associate them with projects const projectErrors = new Array<{ - project: WorkspaceProject + project: TestProject title: string error: unknown file?: File diff --git a/packages/vitest/src/node/reporters/index.ts b/packages/vitest/src/node/reporters/index.ts index dbeac3434e1c..f1c1c7449df1 100644 --- a/packages/vitest/src/node/reporters/index.ts +++ b/packages/vitest/src/node/reporters/index.ts @@ -30,7 +30,7 @@ export { } export type { BaseReporter, Reporter } -export type { TestProject } from '../reported-workspace-project' +export type { TestProject } from '../project' /** * @deprecated Use `TestModule` instead */ diff --git a/packages/vitest/src/node/reporters/reported-tasks.ts b/packages/vitest/src/node/reporters/reported-tasks.ts index 9cc4c6b3cbd2..973570d6c4f4 100644 --- a/packages/vitest/src/node/reporters/reported-tasks.ts +++ b/packages/vitest/src/node/reporters/reported-tasks.ts @@ -7,8 +7,7 @@ import type { TaskMeta, } from '@vitest/runner' import type { TestError } from '@vitest/utils' -import type { WorkspaceProject } from '../workspace' -import { TestProject } from '../reported-workspace-project' +import type { TestProject } from '../project' class ReportedTaskImplementation { /** @@ -36,10 +35,10 @@ class ReportedTaskImplementation { protected constructor( task: RunnerTask, - project: WorkspaceProject, + project: TestProject, ) { this.task = task - this.project = project.testProject || (project.testProject = new TestProject(project)) + this.project = project this.id = task.id this.location = task.location } @@ -47,7 +46,7 @@ class ReportedTaskImplementation { /** * Creates a new reported task instance and stores it in the project's state for future use. */ - static register(task: RunnerTask, project: WorkspaceProject) { + static register(task: RunnerTask, project: TestProject) { const state = new this(task, project) as TestCase | TestSuite | TestModule storeTask(project, task, state) return state @@ -80,7 +79,7 @@ export class TestCase extends ReportedTaskImplementation { */ public readonly parent: TestSuite | TestModule - protected constructor(task: RunnerTestCase | RunnerCustomCase, project: WorkspaceProject) { + protected constructor(task: RunnerTestCase | RunnerCustomCase, project: TestProject) { super(task, project) this.name = task.name @@ -184,9 +183,9 @@ export class TestCase extends ReportedTaskImplementation { class TestCollection { #task: RunnerTestSuite | RunnerTestFile - #project: WorkspaceProject + #project: TestProject - constructor(task: RunnerTestSuite | RunnerTestFile, project: WorkspaceProject) { + constructor(task: RunnerTestSuite | RunnerTestFile, project: TestProject) { this.#task = task this.#project = project } @@ -296,7 +295,7 @@ abstract class SuiteImplementation extends ReportedTaskImplementation { */ public readonly children: TestCollection - protected constructor(task: RunnerTestSuite | RunnerTestFile, project: WorkspaceProject) { + protected constructor(task: RunnerTestSuite | RunnerTestFile, project: TestProject) { super(task, project) this.children = new TestCollection(task, project) } @@ -328,7 +327,7 @@ export class TestSuite extends SuiteImplementation { */ public readonly options: TaskOptions - protected constructor(task: RunnerTestSuite, project: WorkspaceProject) { + protected constructor(task: RunnerTestSuite, project: TestProject) { super(task, project) this.name = task.name @@ -371,7 +370,7 @@ export class TestModule extends SuiteImplementation { */ public readonly moduleId: string - protected constructor(task: RunnerTestFile, project: WorkspaceProject) { + protected constructor(task: RunnerTestFile, project: TestProject) { super(task, project) this.moduleId = task.filepath } @@ -523,18 +522,18 @@ function getTestState(test: TestCase): TestResult['state'] | 'running' { } function storeTask( - project: WorkspaceProject, + project: TestProject, runnerTask: RunnerTask, reportedTask: TestCase | TestSuite | TestModule, ): void { - project.ctx.state.reportedTasksMap.set(runnerTask, reportedTask) + project.vitest.state.reportedTasksMap.set(runnerTask, reportedTask) } function getReportedTask( - project: WorkspaceProject, + project: TestProject, runnerTask: RunnerTask, ): TestCase | TestSuite | TestModule { - const reportedTask = project.ctx.state.getReportedEntity(runnerTask) + const reportedTask = project.vitest.state.getReportedEntity(runnerTask) if (!reportedTask) { throw new Error( `Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`, diff --git a/packages/vitest/src/node/spec.ts b/packages/vitest/src/node/spec.ts index fad253934adc..5f7065cb3819 100644 --- a/packages/vitest/src/node/spec.ts +++ b/packages/vitest/src/node/spec.ts @@ -1,13 +1,12 @@ import type { SerializedTestSpecification } from '../runtime/types/utils' -import type { TestProject } from './reported-workspace-project' +import type { TestProject } from './project' import type { Pool } from './types/pool-options' -import type { WorkspaceProject } from './workspace' export class TestSpecification { /** * @deprecated use `project` instead */ - public readonly 0: WorkspaceProject + public readonly 0: TestProject /** * @deprecated use `moduleId` instead */ @@ -23,7 +22,7 @@ export class TestSpecification { // public readonly location: WorkspaceSpecLocation | undefined constructor( - workspaceProject: WorkspaceProject, + workspaceProject: TestProject, moduleId: string, pool: Pool, // location?: WorkspaceSpecLocation | undefined, @@ -31,7 +30,7 @@ export class TestSpecification { this[0] = workspaceProject this[1] = moduleId this[2] = { pool } - this.project = workspaceProject.testProject + this.project = workspaceProject this.moduleId = moduleId this.pool = pool // this.location = location @@ -53,7 +52,7 @@ export class TestSpecification { * @deprecated */ *[Symbol.iterator]() { - yield this.project.workspaceProject + yield this.project yield this.moduleId yield this.pool } diff --git a/packages/vitest/src/node/state.ts b/packages/vitest/src/node/state.ts index efeef50f87d7..3630d821e09c 100644 --- a/packages/vitest/src/node/state.ts +++ b/packages/vitest/src/node/state.ts @@ -1,6 +1,6 @@ import type { File, Task, TaskResultPack } from '@vitest/runner' import type { UserConsoleLog } from '../types/general' -import type { WorkspaceProject } from './workspace' +import type { TestProject } from './project' import { createFileTask } from '@vitest/runner/utils' import { TestCase, TestModule, TestSuite } from './reporters/reported-tasks' @@ -106,7 +106,7 @@ export class StateManager { }) } - collectFiles(project: WorkspaceProject, files: File[] = []) { + collectFiles(project: TestProject, files: File[] = []) { files.forEach((file) => { const existing = this.filesMap.get(file.filepath) || [] const otherFiles = existing.filter( @@ -127,7 +127,7 @@ export class StateManager { } clearFiles( - project: WorkspaceProject, + project: TestProject, paths: string[] = [], ) { paths.forEach((path) => { @@ -157,7 +157,7 @@ export class StateManager { }) } - updateId(task: Task, project: WorkspaceProject) { + updateId(task: Task, project: TestProject) { if (this.idMap.get(task.id) === task) { return } @@ -214,7 +214,7 @@ export class StateManager { ).length } - cancelFiles(files: string[], project: WorkspaceProject) { + cancelFiles(files: string[], project: TestProject) { this.collectFiles( project, files.map(filepath => diff --git a/packages/vitest/src/node/types/browser.ts b/packages/vitest/src/node/types/browser.ts index 243fdfcbc614..19e2b9f2a6df 100644 --- a/packages/vitest/src/node/types/browser.ts +++ b/packages/vitest/src/node/types/browser.ts @@ -2,7 +2,7 @@ import type { CancelReason } from '@vitest/runner' import type { Awaitable, ErrorWithDiff, ParsedStack } from '@vitest/utils' import type { StackTraceParserOptions } from '@vitest/utils/source-map' import type { ViteDevServer } from 'vite' -import type { WorkspaceProject } from '../workspace' +import type { TestProject } from '../project' import type { ApiConfig } from './config' export interface BrowserProviderInitializationOptions { @@ -32,7 +32,7 @@ export interface BrowserProvider { close: () => Awaitable // eslint-disable-next-line ts/method-signature-style -- we want to allow extended options initialize( - ctx: WorkspaceProject, + ctx: TestProject, options: BrowserProviderInitializationOptions ): Awaitable } @@ -180,7 +180,7 @@ export interface BrowserConfigOptions { export interface BrowserCommandContext { testPath: string | undefined provider: BrowserProvider - project: WorkspaceProject + project: TestProject contextId: string } diff --git a/packages/vitest/src/node/types/config.ts b/packages/vitest/src/node/types/config.ts index fe600405ddc1..ff04ff7bb6fd 100644 --- a/packages/vitest/src/node/types/config.ts +++ b/packages/vitest/src/node/types/config.ts @@ -1101,7 +1101,8 @@ export type ProjectConfig = Omit< export type ResolvedProjectConfig = Omit< ResolvedConfig, - NonProjectOptions + // some options cannot be set, but they are inherited from the workspace + Exclude > export interface UserWorkspaceConfig extends ViteUserConfig { diff --git a/packages/vitest/src/node/workspace/resolveWorkspace.ts b/packages/vitest/src/node/workspace/resolveWorkspace.ts index 4f7be66f12b2..9dd4f8346417 100644 --- a/packages/vitest/src/node/workspace/resolveWorkspace.ts +++ b/packages/vitest/src/node/workspace/resolveWorkspace.ts @@ -1,6 +1,6 @@ import type { Vitest } from '../core' +import type { TestProject } from '../project' import type { UserConfig, UserWorkspaceConfig, WorkspaceProjectConfiguration } from '../types/config' -import type { WorkspaceProject } from '../workspace' import { existsSync, promises as fs } from 'node:fs' import os from 'node:os' import { limitConcurrency } from '@vitest/runner/utils' @@ -8,7 +8,7 @@ import fg from 'fast-glob' import { relative, resolve } from 'pathe' import { mergeConfig } from 'vite' import { configFiles as defaultConfigFiles } from '../../constants' -import { initializeProject } from '../workspace' +import { initializeProject } from '../project' import { isDynamicPattern } from './fast-glob-pattern' export async function resolveWorkspace( @@ -16,7 +16,7 @@ export async function resolveWorkspace( cliOptions: UserConfig, workspaceConfigPath: string, workspaceDefinition: WorkspaceProjectConfiguration[], -): Promise { +): Promise { const { configFiles, projectConfigs, nonConfigDirectories } = await resolveWorkspaceProjectConfigs( vitest, workspaceConfigPath, @@ -50,7 +50,7 @@ export async function resolveWorkspace( return acc }, {} as UserConfig) - const projectPromises: Promise[] = [] + const projectPromises: Promise[] = [] const fileProjects = [...configFiles, ...nonConfigDirectories] const concurrent = limitConcurrency(os.availableParallelism?.() || os.cpus().length || 5) diff --git a/packages/vitest/src/public/node.ts b/packages/vitest/src/public/node.ts index c8ba9ef7679f..cf8485ced728 100644 --- a/packages/vitest/src/public/node.ts +++ b/packages/vitest/src/public/node.ts @@ -15,8 +15,8 @@ export { resolveFsAllow } from '../node/plugins/utils' export type { ProcessPool, WorkspaceSpec } from '../node/pool' export { getFilePoolName } from '../node/pool' export { createMethodsRPC } from '../node/pools/rpc' -export { TestProject } from '../node/reported-workspace-project' -export type { SerializedTestProject } from '../node/reported-workspace-project' +export type { SerializedTestProject, TestProject } from '../node/project' +export type { WorkspaceProject } from '../node/project' export type { HTMLOptions } from '../node/reporters/html' export type { JsonOptions } from '../node/reporters/json' @@ -110,7 +110,6 @@ export type { export const TestFile = _TestFile export type { WorkerContext } from '../node/types/worker' export { createViteLogger } from '../node/viteLogger' -export type { WorkspaceProject } from '../node/workspace' /** * @deprecated Use `ModuleDiagnostic` instead diff --git a/packages/vitest/src/typecheck/collect.ts b/packages/vitest/src/typecheck/collect.ts index a3d4ed864dd8..91088b97ec1d 100644 --- a/packages/vitest/src/typecheck/collect.ts +++ b/packages/vitest/src/typecheck/collect.ts @@ -1,6 +1,6 @@ import type { File, Suite, Test } from '@vitest/runner' import type { RawSourceMap } from 'vite-node' -import type { WorkspaceProject } from '../node/workspace' +import type { TestProject } from '../node/project' import { calculateSuiteHash, generateHash, @@ -44,7 +44,7 @@ export interface FileInformation { } export async function collectTests( - ctx: WorkspaceProject, + ctx: TestProject, filepath: string, ): Promise { const request = await ctx.vitenode.transformRequest(filepath, filepath) diff --git a/packages/vitest/src/typecheck/typechecker.ts b/packages/vitest/src/typecheck/typechecker.ts index 22fcd4c9b866..5b8710ee59d5 100644 --- a/packages/vitest/src/typecheck/typechecker.ts +++ b/packages/vitest/src/typecheck/typechecker.ts @@ -3,7 +3,7 @@ import type { File, Task, TaskResultPack, TaskState } from '@vitest/runner' import type { ParsedStack } from '@vitest/utils' import type { ChildProcess } from 'node:child_process' import type { Vitest } from '../node/core' -import type { WorkspaceProject } from '../node/workspace' +import type { TestProject } from '../node/project' import type { Awaitable } from '../types/general' import type { FileInformation } from './collect' import type { TscErrorInfo } from './types' @@ -54,7 +54,7 @@ export class Typechecker { protected files: string[] = [] - constructor(protected ctx: WorkspaceProject) {} + constructor(protected ctx: TestProject) {} public setFiles(files: string[]) { this.files = files From 14c6bb6d652cb436fb66a0267560f58f5915378c Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 14:49:14 +0100 Subject: [PATCH 02/16] chore: replace deprecated methods --- packages/browser/src/node/commands/fs.ts | 4 ++-- packages/vitest/src/node/core.ts | 4 ++-- packages/vitest/src/node/pools/rpc.ts | 6 +++--- packages/vitest/src/node/reporters/blob.ts | 6 +++--- packages/vitest/src/node/workspace/resolveWorkspace.ts | 4 ++-- packages/vitest/src/utils/graph.ts | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/browser/src/node/commands/fs.ts b/packages/browser/src/node/commands/fs.ts index 7dc30924250a..79eabd36f332 100644 --- a/packages/browser/src/node/commands/fs.ts +++ b/packages/browser/src/node/commands/fs.ts @@ -7,8 +7,8 @@ import { isFileServingAllowed } from 'vitest/node' function assertFileAccess(path: string, project: WorkspaceProject) { if ( - !isFileServingAllowed(path, project.server) - && !isFileServingAllowed(path, project.ctx.server) + !isFileServingAllowed(path, project.vite) + && !isFileServingAllowed(path, project.vitest.server) ) { throw new Error( `Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`, diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index d39733d27622..8a73d4c45ba4 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -460,14 +460,14 @@ export class Vitest { } deps.add(filepath) - const mod = project.server.moduleGraph.getModuleById(filepath) + const mod = project.vite.moduleGraph.getModuleById(filepath) const transformed = mod?.ssrTransformResult || await project.vitenode.transformRequest(filepath) if (!transformed) { return } const dependencies = [...transformed.deps || [], ...transformed.dynamicDeps || []] await Promise.all(dependencies.map(async (dep) => { - const path = await project.server.pluginContainer.resolveId(dep, filepath, { ssr: true }) + const path = await project.vite.pluginContainer.resolveId(dep, filepath, { ssr: true }) const fsPath = path && !path.external && path.id.split('?')[0] if (fsPath && !fsPath.includes('node_modules') && !deps.has(fsPath) && existsSync(fsPath)) { await addImports(project, fsPath) diff --git a/packages/vitest/src/node/pools/rpc.ts b/packages/vitest/src/node/pools/rpc.ts index 43694e3a24aa..4c41417fdae8 100644 --- a/packages/vitest/src/node/pools/rpc.ts +++ b/packages/vitest/src/node/pools/rpc.ts @@ -22,14 +22,14 @@ export function createMethodsRPC(project: TestProject, options: MethodsOptions = }, resolveSnapshotPath(testPath: string) { return ctx.snapshot.resolvePath(testPath, { - config: project.getSerializableConfig(), + config: project.serializedConfig, }) }, async getSourceMap(id, force) { if (force) { - const mod = project.server.moduleGraph.getModuleById(id) + const mod = project.vite.moduleGraph.getModuleById(id) if (mod) { - project.server.moduleGraph.invalidateModule(mod) + project.vite.moduleGraph.invalidateModule(mod) } } const r = await project.vitenode.transformRequest(id) diff --git a/packages/vitest/src/node/reporters/blob.ts b/packages/vitest/src/node/reporters/blob.ts index 0b0d80231b55..da7678c1701a 100644 --- a/packages/vitest/src/node/reporters/blob.ts +++ b/packages/vitest/src/node/reporters/blob.ts @@ -46,7 +46,7 @@ export class BlobReporter implements Reporter { (project) => { return [ project.getName(), - [...project.server.moduleGraph.idToModuleMap.entries()].map((mod) => { + [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => { if (!mod[1].file) { return null } @@ -136,10 +136,10 @@ export async function readBlobs( return } moduleIds.forEach(([moduleId, file, url]) => { - const moduleNode = project.server.moduleGraph.createFileOnlyEntry(file) + const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file) moduleNode.url = url moduleNode.id = moduleId - project.server.moduleGraph.idToModuleMap.set(moduleId, moduleNode) + project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode) }) }) }) diff --git a/packages/vitest/src/node/workspace/resolveWorkspace.ts b/packages/vitest/src/node/workspace/resolveWorkspace.ts index 9dd4f8346417..079c36bca9f3 100644 --- a/packages/vitest/src/node/workspace/resolveWorkspace.ts +++ b/packages/vitest/src/node/workspace/resolveWorkspace.ts @@ -100,9 +100,9 @@ export async function resolveWorkspace( : [' '] throw new Error([ `Project name "${name}"`, - project.server.config.configFile ? ` from "${relative(vitest.config.root, project.server.config.configFile)}"` : '', + project.vite.config.configFile ? ` from "${relative(vitest.config.root, project.vite.config.configFile)}"` : '', ' is not unique.', - duplicate?.server.config.configFile ? ` The project is already defined by "${relative(vitest.config.root, duplicate.server.config.configFile)}".` : '', + duplicate?.vite.config.configFile ? ` The project is already defined by "${relative(vitest.config.root, duplicate.vite.config.configFile)}".` : '', filesError, 'All projects in a workspace should have unique names. Make sure your configuration is correct.', ].join('')) diff --git a/packages/vitest/src/utils/graph.ts b/packages/vitest/src/utils/graph.ts index 57cab5b14d45..3e3cd783524e 100644 --- a/packages/vitest/src/utils/graph.ts +++ b/packages/vitest/src/utils/graph.ts @@ -51,7 +51,7 @@ export async function getModuleGraph( await get(project.browser.vite.moduleGraph.getModuleById(id)) } else { - await get(project.server.moduleGraph.getModuleById(id)) + await get(project.vite.moduleGraph.getModuleById(id)) } return { From f7a4a433769f54e39c1611ee7dd1386877b18742 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 14:51:22 +0100 Subject: [PATCH 03/16] chore: rename isCore to isRootProject --- packages/vitest/src/node/logger.ts | 8 +++----- packages/vitest/src/node/project.ts | 9 +++++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index 40f9ea9f4273..ad73c60fa26f 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -163,8 +163,7 @@ export class Logger { } this.ctx.projects.forEach((project) => { const config = project.config - const name = project.getName() - const output = project.isCore() || !name ? '' : `[${name}]` + const output = project.isRootProject() || !project.name ? '' : `[${project.name}]` if (output) { this.console.error(c.bgCyan(`${output} Config`)) } @@ -256,10 +255,9 @@ export class Logger { return } - const name = project.getName() - const output = project.isCore() + const output = project.isRootProject() ? '' - : formatProjectName(name) + : formatProjectName(project.name) const provider = project.browser.provider.name const providerString = provider === 'preview' ? '' : ` by ${c.reset(c.bold(provider))}` this.log( diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index e815bd0925b5..7c3e0f06283f 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -127,7 +127,7 @@ export class TestProject { * Get the provided context. The project context is merged with the global context. */ getProvidedContext(): ProvidedContext { - if (this.isCore()) { + if (this.isRootProject()) { return this._provided } // globalSetup can run even if core workspace is not part of the test run @@ -200,10 +200,15 @@ export class TestProject { /** * Is this the root project. The root project is the one that has the root config. */ - public isCore(): boolean { + public isRootProject(): boolean { return this.vitest.getCoreWorkspaceProject() === this } + /** @deprecated use `isRootProject` instead */ + public isCore(): boolean { + return this.isRootProject() + } + /** @deprecated use createSpecification instead */ public createSpec(moduleId: string, pool: string): DeprecatedWorkspaceSpec { return new TestSpecification(this, moduleId, pool) as DeprecatedWorkspaceSpec From 32686db9e4db2eed8c0fb55dfd8b45a100ab3ecd Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 15:02:27 +0100 Subject: [PATCH 04/16] test: update mock --- test/core/test/sequencers.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/core/test/sequencers.test.ts b/test/core/test/sequencers.test.ts index 28403b042434..0850cd5f44cb 100644 --- a/test/core/test/sequencers.test.ts +++ b/test/core/test/sequencers.test.ts @@ -19,9 +19,7 @@ function buildCtx() { function buildWorkspace() { return { - testProject: { - name: 'test', - }, + name: 'test', } as any as WorkspaceProject } From 7d7bc12cd478120e10a8f5c747b315358afef676 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 15:11:45 +0100 Subject: [PATCH 05/16] fix: replace @private with @internal --- packages/vitest/src/node/project.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index 7c3e0f06283f..67a803fa71a0 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -219,7 +219,7 @@ export class TestProject { return this._initializeGlobalSetup() } - /** @private */ + /** @internal */ async _initializeGlobalSetup() { if (this._globalSetups) { return @@ -253,7 +253,7 @@ export class TestProject { return this._teardownGlobalSetup() } - /** @private */ + /** @internal */ async _teardownGlobalSetup() { if (!this._globalSetups) { return @@ -379,14 +379,14 @@ export class TestProject { return isBrowserEnabled(this.config) } - /** @private */ + /** @internal */ _markTestFile(testPath: string): void { this.testFilesList?.push(testPath) } /** * Returns if the file is a test file. Requires `.globTestFiles()` to be called first. - * @private + * @internal */ isTestFile(testPath: string): boolean { return !!this.testFilesList && this.testFilesList.includes(testPath) @@ -394,7 +394,7 @@ export class TestProject { /** * Returns if the file is a typecheck test file. Requires `.globTestFiles()` to be called first. - * @private + * @internal */ isTypecheckFile(testPath: string): boolean { return !!this.typecheckFilesList && this.typecheckFilesList.includes(testPath) @@ -405,7 +405,7 @@ export class TestProject { return this._serializeOverridenConfig() } - /** @private */ + /** @internal */ async globFiles(include: string[], exclude: string[], cwd: string) { const globOptions: fg.Options = { dot: true, @@ -478,7 +478,7 @@ export class TestProject { return testFiles } - /** @private */ + /** @internal */ async _initBrowserServer() { if (!this.isBrowserEnabled() || this.browser) { return @@ -534,7 +534,7 @@ export class TestProject { return this._configureServer(options, server) } - /** @private */ + /** @internal */ async _configureServer(options: UserConfig, server: ViteDevServer): Promise { this._config = resolveConfig( this.vitest.mode, @@ -601,7 +601,7 @@ export class TestProject { return this._initBrowserProvider() } - /** @private */ + /** @internal */ async _initBrowserProvider(): Promise { if (!this.isBrowserEnabled() || this.browser?.provider) { return From 33b5275c1fc61a23f4e379ffe884490ffa971995 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 15:17:36 +0100 Subject: [PATCH 06/16] fix: strip internal --- packages/vitest/src/node/project.ts | 2 +- tsconfig.base.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index 67a803fa71a0..2568de6463a4 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -209,7 +209,7 @@ export class TestProject { return this.isRootProject() } - /** @deprecated use createSpecification instead */ + /** @deprecated use `createSpecification` instead */ public createSpec(moduleId: string, pool: string): DeprecatedWorkspaceSpec { return new TestSpecification(this, moduleId, pool) as DeprecatedWorkspaceSpec } diff --git a/tsconfig.base.json b/tsconfig.base.json index 0b7c334df5be..28a1fd4b24c6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -38,6 +38,7 @@ "strict": true, "declaration": true, "noEmit": true, + "stripInternal": true, "esModuleInterop": true, "skipLibCheck": true } From cf9276fbb73bae6a0d4f6a34c291052eede56b8c Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 15:24:56 +0100 Subject: [PATCH 07/16] chore: remove-id --- packages/vitest/src/node/project.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index 2568de6463a4..b4889f0bbc0d 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -70,8 +70,7 @@ export class TestProject { /** @deprecated use the current project instead */ public testProject: TestProject - public readonly id = nanoid() - public readonly tmpDir = join(tmpdir(), this.id) + public readonly tmpDir = join(tmpdir(), nanoid()) vitenode!: ViteNodeServer runner!: ViteNodeRunner From 0bfbf103ad1e6620ffcc15eaa67fdce020658c36 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 15:45:10 +0100 Subject: [PATCH 08/16] chore: replace deprecated --- packages/browser/src/node/commands/fs.ts | 4 ++-- packages/browser/src/node/index.ts | 4 ++-- packages/browser/src/node/plugin.ts | 4 ++-- .../browser/src/node/providers/playwright.ts | 22 +++++++++---------- .../browser/src/node/providers/preview.ts | 6 ++--- .../browser/src/node/providers/webdriver.ts | 12 +++++----- packages/browser/src/node/server.ts | 4 ++-- packages/browser/src/node/utils.ts | 4 ++-- packages/coverage-v8/src/provider.ts | 4 ++-- packages/vitest/src/api/setup.ts | 2 +- packages/vitest/src/node/core.ts | 21 +++++++++++------- packages/vitest/src/node/logger.ts | 2 +- packages/vitest/src/node/pools/typecheck.ts | 4 ++-- packages/vitest/src/node/project.ts | 4 ++-- packages/vitest/src/node/reporters/base.ts | 2 +- .../src/node/reporters/github-actions.ts | 2 +- packages/vitest/src/node/spec.ts | 6 ++--- packages/vitest/src/node/types/config.ts | 5 ++++- .../src/node/workspace/resolveWorkspace.ts | 10 ++++----- packages/vitest/src/public/config.ts | 6 ++--- packages/vitest/src/utils/test-helpers.ts | 2 +- 21 files changed, 69 insertions(+), 61 deletions(-) diff --git a/packages/browser/src/node/commands/fs.ts b/packages/browser/src/node/commands/fs.ts index 79eabd36f332..fba82a384c86 100644 --- a/packages/browser/src/node/commands/fs.ts +++ b/packages/browser/src/node/commands/fs.ts @@ -1,11 +1,11 @@ -import type { BrowserCommand, WorkspaceProject } from 'vitest/node' +import type { BrowserCommand, TestProject } from 'vitest/node' import type { BrowserCommands } from '../../../context' import fs, { promises as fsp } from 'node:fs' import { basename, dirname, resolve } from 'node:path' import mime from 'mime/lite' import { isFileServingAllowed } from 'vitest/node' -function assertFileAccess(path: string, project: WorkspaceProject) { +function assertFileAccess(path: string, project: TestProject) { if ( !isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.server) diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts index 7d91208dee5b..c7af212b3c98 100644 --- a/packages/browser/src/node/index.ts +++ b/packages/browser/src/node/index.ts @@ -1,5 +1,5 @@ import type { Plugin } from 'vitest/config' -import type { WorkspaceProject } from 'vitest/node' +import type { TestProject } from 'vitest/node' import c from 'tinyrainbow' import { createViteLogger, createViteServer } from 'vitest/node' import { version } from '../../package.json' @@ -13,7 +13,7 @@ export { createBrowserPool } from './pool' export type { BrowserServer } from './server' export async function createBrowserServer( - project: WorkspaceProject, + project: TestProject, configFile: string | undefined, prePlugins: Plugin[] = [], postPlugins: Plugin[] = [], diff --git a/packages/browser/src/node/plugin.ts b/packages/browser/src/node/plugin.ts index e1d049d7c702..f52a5b0b0015 100644 --- a/packages/browser/src/node/plugin.ts +++ b/packages/browser/src/node/plugin.ts @@ -1,6 +1,6 @@ import type { Stats } from 'node:fs' import type { HtmlTagDescriptor } from 'vite' -import type { WorkspaceProject } from 'vitest/node' +import type { TestProject } from 'vitest/node' import type { BrowserServer } from './server' import { lstatSync, readFileSync } from 'node:fs' import { createRequire } from 'node:module' @@ -583,7 +583,7 @@ function getRequire() { return _require } -function resolveCoverageFolder(project: WorkspaceProject) { +function resolveCoverageFolder(project: TestProject) { const options = project.ctx.config const htmlReporter = options.coverage?.enabled ? toArray(options.coverage.reporter).find((reporter) => { diff --git a/packages/browser/src/node/providers/playwright.ts b/packages/browser/src/node/providers/playwright.ts index cb24179e8479..fa9bf02b678f 100644 --- a/packages/browser/src/node/providers/playwright.ts +++ b/packages/browser/src/node/providers/playwright.ts @@ -9,7 +9,7 @@ import type { import type { BrowserProvider, BrowserProviderInitializationOptions, - WorkspaceProject, + TestProject, } from 'vitest/node' export const playwrightBrowsers = ['firefox', 'webkit', 'chromium'] as const @@ -27,7 +27,7 @@ export class PlaywrightBrowserProvider implements BrowserProvider { public browser: Browser | null = null private browserName!: PlaywrightBrowser - private ctx!: WorkspaceProject + private project!: TestProject private options?: { launch?: LaunchOptions @@ -44,10 +44,10 @@ export class PlaywrightBrowserProvider implements BrowserProvider { } initialize( - project: WorkspaceProject, + project: TestProject, { browser, options }: PlaywrightProviderOptions, ) { - this.ctx = project + this.project = project this.browserName = browser this.options = options as any } @@ -62,7 +62,7 @@ export class PlaywrightBrowserProvider implements BrowserProvider { } this.browserPromise = (async () => { - const options = this.ctx.config.browser + const options = this.project.config.browser const playwright = await import('playwright') @@ -71,20 +71,20 @@ export class PlaywrightBrowserProvider implements BrowserProvider { headless: options.headless, } satisfies LaunchOptions - if (this.ctx.config.inspector.enabled) { + if (this.project.config.inspector.enabled) { // NodeJS equivalent defaults: https://nodejs.org/en/learn/getting-started/debugging#enable-inspector - const port = this.ctx.config.inspector.port || 9229 - const host = this.ctx.config.inspector.host || '127.0.0.1' + const port = this.project.config.inspector.port || 9229 + const host = this.project.config.inspector.host || '127.0.0.1' launchOptions.args ||= [] launchOptions.args.push(`--remote-debugging-port=${port}`) launchOptions.args.push(`--remote-debugging-address=${host}`) - this.ctx.logger.log(`Debugger listening on ws://${host}:${port}`) + this.project.logger.log(`Debugger listening on ws://${host}:${port}`) } // start Vitest UI maximized only on supported browsers - if (this.ctx.config.browser.ui && this.browserName === 'chromium') { + if (this.project.config.browser.ui && this.browserName === 'chromium') { if (!launchOptions.args) { launchOptions.args = [] } @@ -113,7 +113,7 @@ export class PlaywrightBrowserProvider implements BrowserProvider { ignoreHTTPSErrors: true, serviceWorkers: 'allow', } satisfies BrowserContextOptions - if (this.ctx.config.browser.ui) { + if (this.project.config.browser.ui) { options.viewport = null } const context = await browser.newContext(options) diff --git a/packages/browser/src/node/providers/preview.ts b/packages/browser/src/node/providers/preview.ts index a8e8829a7cfd..2a495996d469 100644 --- a/packages/browser/src/node/providers/preview.ts +++ b/packages/browser/src/node/providers/preview.ts @@ -1,9 +1,9 @@ -import type { BrowserProvider, WorkspaceProject } from 'vitest/node' +import type { BrowserProvider, TestProject } from 'vitest/node' export class PreviewBrowserProvider implements BrowserProvider { public name = 'preview' as const public supportsParallelism: boolean = false - private project!: WorkspaceProject + private project!: TestProject private open = false getSupportedBrowsers() { @@ -19,7 +19,7 @@ export class PreviewBrowserProvider implements BrowserProvider { return {} } - async initialize(project: WorkspaceProject) { + async initialize(project: TestProject) { this.project = project this.open = false if (project.config.browser.headless) { diff --git a/packages/browser/src/node/providers/webdriver.ts b/packages/browser/src/node/providers/webdriver.ts index 810d91ab1916..c28584f42b1b 100644 --- a/packages/browser/src/node/providers/webdriver.ts +++ b/packages/browser/src/node/providers/webdriver.ts @@ -1,7 +1,7 @@ import type { BrowserProvider, BrowserProviderInitializationOptions, - WorkspaceProject, + TestProject, } from 'vitest/node' import type { RemoteOptions } from 'webdriverio' @@ -20,7 +20,7 @@ export class WebdriverBrowserProvider implements BrowserProvider { public browser: WebdriverIO.Browser | null = null private browserName!: WebdriverBrowser - private ctx!: WorkspaceProject + private project!: TestProject private options?: RemoteOptions @@ -29,10 +29,10 @@ export class WebdriverBrowserProvider implements BrowserProvider { } async initialize( - ctx: WorkspaceProject, + ctx: TestProject, { browser, options }: WebdriverProviderOptions, ) { - this.ctx = ctx + this.project = ctx this.browserName = browser this.options = options as RemoteOptions } @@ -61,7 +61,7 @@ export class WebdriverBrowserProvider implements BrowserProvider { return this.browser } - const options = this.ctx.config.browser + const options = this.project.config.browser if (this.browserName === 'safari') { if (options.headless) { @@ -95,7 +95,7 @@ export class WebdriverBrowserProvider implements BrowserProvider { edge: ['ms:edgeOptions', ['--headless']], } as const - const options = this.ctx.config.browser + const options = this.project.config.browser const browser = this.browserName if (browser !== 'safari' && options.headless) { const [key, args] = headlessMap[browser] diff --git a/packages/browser/src/node/server.ts b/packages/browser/src/node/server.ts index a07991d95647..f50aaf34892d 100644 --- a/packages/browser/src/node/server.ts +++ b/packages/browser/src/node/server.ts @@ -5,8 +5,8 @@ import type { BrowserScript, CDPSession, BrowserServer as IBrowserServer, + TestProject, Vite, - WorkspaceProject, } from 'vitest/node' import { existsSync } from 'node:fs' import { readFile } from 'node:fs/promises' @@ -42,7 +42,7 @@ export class BrowserServer implements IBrowserServer { private stackTraceOptions: StackTraceParserOptions constructor( - public project: WorkspaceProject, + public project: TestProject, public base: string, ) { this.stackTraceOptions = { diff --git a/packages/browser/src/node/utils.ts b/packages/browser/src/node/utils.ts index b1b3c206b7f1..3d3e92ad37c5 100644 --- a/packages/browser/src/node/utils.ts +++ b/packages/browser/src/node/utils.ts @@ -1,4 +1,4 @@ -import type { BrowserProviderModule, ResolvedBrowserOptions, WorkspaceProject } from 'vitest/node' +import type { BrowserProviderModule, ResolvedBrowserOptions, TestProject } from 'vitest/node' export function replacer(code: string, values: Record) { return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _) @@ -8,7 +8,7 @@ const builtinProviders = ['webdriverio', 'playwright', 'preview'] export async function getBrowserProvider( options: ResolvedBrowserOptions, - project: WorkspaceProject, + project: TestProject, ): Promise { if (options.provider == null || builtinProviders.includes(options.provider)) { const providers = await import('./providers') diff --git a/packages/coverage-v8/src/provider.ts b/packages/coverage-v8/src/provider.ts index 340d9457ccef..f5942989a7d4 100644 --- a/packages/coverage-v8/src/provider.ts +++ b/packages/coverage-v8/src/provider.ts @@ -2,7 +2,7 @@ import type { CoverageMap } from 'istanbul-lib-coverage' import type { Profiler } from 'node:inspector' import type { EncodedSourceMap, FetchResult } from 'vite-node' import type { AfterSuiteRunMeta } from 'vitest' -import type { CoverageProvider, ReportContext, ResolvedCoverageOptions, Vitest, WorkspaceProject } from 'vitest/node' +import type { CoverageProvider, ReportContext, ResolvedCoverageOptions, TestProject, Vitest } from 'vitest/node' import { promises as fs } from 'node:fs' import { fileURLToPath, pathToFileURL } from 'node:url' import remapping from '@ampproject/remapping' @@ -288,7 +288,7 @@ export class V8CoverageProvider extends BaseCoverageProvider { let fetchCache = project.vitenode.fetchCache diff --git a/packages/vitest/src/api/setup.ts b/packages/vitest/src/api/setup.ts index f0554d62e2b4..6d4621a7696c 100644 --- a/packages/vitest/src/api/setup.ts +++ b/packages/vitest/src/api/setup.ts @@ -76,7 +76,7 @@ export function setup(ctx: Vitest, _server?: ViteDevServer) { await ctx.rerunFiles(files) }, getConfig() { - return ctx.getCoreWorkspaceProject().getSerializableConfig() + return ctx.getRootTestProject().serializedConfig }, async getTransformResult(projectName: string, id, browser = false) { const project = ctx.getProjectByName(projectName) diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 8a73d4c45ba4..5d6b411a1ef6 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -204,7 +204,7 @@ export class Vitest { } public provide(key: T, value: ProvidedContext[T]) { - this.getCoreWorkspaceProject().provide(key, value) + this.getRootTestProject().provide(key, value) } /** @@ -222,24 +222,29 @@ export class Vitest { return this.coreWorkspaceProject } + /** @deprecated use `getRootTestProject` */ public getCoreWorkspaceProject(): TestProject { return this.coreWorkspaceProject } + public getRootTestProject(): TestProject { + return this.coreWorkspaceProject + } + /** * @deprecated use Reported Task API instead */ public getProjectByTaskId(taskId: string): TestProject { const task = this.state.idMap.get(taskId) const projectName = (task as File).projectName || task?.file?.projectName || '' - return this.projects.find(p => p.getName() === projectName) - || this.getCoreWorkspaceProject() + return this.projects.find(p => p.name === projectName) + || this.getRootTestProject() || this.projects[0] } public getProjectByName(name: string = '') { - return this.projects.find(p => p.getName() === name) - || this.getCoreWorkspaceProject() + return this.projects.find(p => p.name === name) + || this.getRootTestProject() || this.projects[0] } @@ -475,7 +480,7 @@ export class Vitest { })) } - await addImports(spec.project.workspaceProject, spec.moduleId) + await addImports(spec.project, spec.moduleId) deps.delete(spec.moduleId) return deps @@ -558,7 +563,7 @@ export class Vitest { async initializeGlobalSetup(paths: TestSpecification[]) { const projects = new Set(paths.map(spec => spec.project)) - const coreProject = this.getCoreWorkspaceProject() + const coreProject = this.getRootTestProject() if (!projects.has(coreProject)) { projects.add(coreProject) } @@ -1125,7 +1130,7 @@ export class Vitest { } /** - * @deprecated use globTestSpecs instead + * @deprecated use `globTestSpecs` instead */ public async globTestFiles(filters: string[] = []) { return this.globTestSpecs(filters) diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index ad73c60fa26f..391b291a97d6 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -105,7 +105,7 @@ export class Logger { printError(err: unknown, options: ErrorOptions = {}): PrintErrorResult | undefined { const { fullStack = false, type } = options const project = options.project - ?? this.ctx.getCoreWorkspaceProject() + ?? this.ctx.getRootTestProject() ?? this.ctx.projects[0] return printError(err, project, { type, diff --git a/packages/vitest/src/node/pools/typecheck.ts b/packages/vitest/src/node/pools/typecheck.ts index e7efac948919..5a0bcae3a9c3 100644 --- a/packages/vitest/src/node/pools/typecheck.ts +++ b/packages/vitest/src/node/pools/typecheck.ts @@ -104,10 +104,10 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool { for (const name in specsByProject) { const project = specsByProject[name][0].project const files = specsByProject[name].map(spec => spec.moduleId) - const checker = await createWorkspaceTypechecker(project.workspaceProject, files) + const checker = await createWorkspaceTypechecker(project, files) checker.setFiles(files) await checker.collectTests() - ctx.state.collectFiles(project.workspaceProject, checker.getTestFiles()) + ctx.state.collectFiles(project, checker.getTestFiles()) await ctx.report('onCollected') } } diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index b4889f0bbc0d..0931579ba840 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -132,7 +132,7 @@ export class TestProject { // globalSetup can run even if core workspace is not part of the test run // so we need to inherit its provided context return { - ...this.vitest.getCoreWorkspaceProject().getProvidedContext(), + ...this.vitest.getRootTestProject().getProvidedContext(), ...this._provided, } } @@ -200,7 +200,7 @@ export class TestProject { * Is this the root project. The root project is the one that has the root config. */ public isRootProject(): boolean { - return this.vitest.getCoreWorkspaceProject() === this + return this.vitest.getRootTestProject() === this } /** @deprecated use `isRootProject` instead */ diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index f4b2d7f4596b..44e7ccc3ed44 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -290,7 +290,7 @@ export abstract class BaseReporter implements Reporter { const project = log.taskId ? this.ctx.getProjectByTaskId(log.taskId) - : this.ctx.getCoreWorkspaceProject() + : this.ctx.getRootTestProject() const stack = log.browser ? (project.browser?.parseStacktrace(log.origin) || []) diff --git a/packages/vitest/src/node/reporters/github-actions.ts b/packages/vitest/src/node/reporters/github-actions.ts index 8f4e5a12d7bd..7d63b7a0bcc2 100644 --- a/packages/vitest/src/node/reporters/github-actions.ts +++ b/packages/vitest/src/node/reporters/github-actions.ts @@ -23,7 +23,7 @@ export class GithubActionsReporter implements Reporter { }>() for (const error of errors) { projectErrors.push({ - project: this.ctx.getCoreWorkspaceProject(), + project: this.ctx.getRootTestProject(), title: 'Unhandled error', error, }) diff --git a/packages/vitest/src/node/spec.ts b/packages/vitest/src/node/spec.ts index 5f7065cb3819..96e12a8580fd 100644 --- a/packages/vitest/src/node/spec.ts +++ b/packages/vitest/src/node/spec.ts @@ -22,15 +22,15 @@ export class TestSpecification { // public readonly location: WorkspaceSpecLocation | undefined constructor( - workspaceProject: TestProject, + project: TestProject, moduleId: string, pool: Pool, // location?: WorkspaceSpecLocation | undefined, ) { - this[0] = workspaceProject + this[0] = project this[1] = moduleId this[2] = { pool } - this.project = workspaceProject + this.project = project this.moduleId = moduleId this.pool = pool // this.location = location diff --git a/packages/vitest/src/node/types/config.ts b/packages/vitest/src/node/types/config.ts index ff04ff7bb6fd..724e1773992b 100644 --- a/packages/vitest/src/node/types/config.ts +++ b/packages/vitest/src/node/types/config.ts @@ -1117,10 +1117,13 @@ export type UserProjectConfigExport = | Promise | UserProjectConfigFn -export type WorkspaceProjectConfiguration = string | (UserProjectConfigExport & { +export type TestProjectConfiguration = string | (UserProjectConfigExport & { /** * Relative path to the extendable config. All other options will be merged with this config. * @example '../vite.config.ts' */ extends?: string }) + +/** @deprecated use `TestProjectConfiguration` instead */ +export type WorkspaceProjectConfiguration = TestProjectConfiguration diff --git a/packages/vitest/src/node/workspace/resolveWorkspace.ts b/packages/vitest/src/node/workspace/resolveWorkspace.ts index 079c36bca9f3..d826b6915054 100644 --- a/packages/vitest/src/node/workspace/resolveWorkspace.ts +++ b/packages/vitest/src/node/workspace/resolveWorkspace.ts @@ -1,6 +1,6 @@ import type { Vitest } from '../core' import type { TestProject } from '../project' -import type { UserConfig, UserWorkspaceConfig, WorkspaceProjectConfiguration } from '../types/config' +import type { TestProjectConfiguration, UserConfig, UserWorkspaceConfig } from '../types/config' import { existsSync, promises as fs } from 'node:fs' import os from 'node:os' import { limitConcurrency } from '@vitest/runner/utils' @@ -15,9 +15,9 @@ export async function resolveWorkspace( vitest: Vitest, cliOptions: UserConfig, workspaceConfigPath: string, - workspaceDefinition: WorkspaceProjectConfiguration[], + workspaceDefinition: TestProjectConfiguration[], ): Promise { - const { configFiles, projectConfigs, nonConfigDirectories } = await resolveWorkspaceProjectConfigs( + const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs( vitest, workspaceConfigPath, workspaceDefinition, @@ -113,10 +113,10 @@ export async function resolveWorkspace( return resolvedProjects } -async function resolveWorkspaceProjectConfigs( +async function resolveTestProjectConfigs( vitest: Vitest, workspaceConfigPath: string, - workspaceDefinition: WorkspaceProjectConfiguration[], + workspaceDefinition: TestProjectConfiguration[], ) { // project configurations that were specified directly const projectsOptions: UserWorkspaceConfig[] = [] diff --git a/packages/vitest/src/public/config.ts b/packages/vitest/src/public/config.ts index 9f8b02110354..fd906108cb68 100644 --- a/packages/vitest/src/public/config.ts +++ b/packages/vitest/src/public/config.ts @@ -1,6 +1,6 @@ import type { ConfigEnv, UserConfig as ViteUserConfig } from 'vite' -import type { UserProjectConfigExport, UserProjectConfigFn, UserWorkspaceConfig, WorkspaceProjectConfiguration } from '../node/types/config' +import type { TestProjectConfiguration, UserProjectConfigExport, UserProjectConfigFn, UserWorkspaceConfig, WorkspaceProjectConfiguration } from '../node/types/config' import '../node/types/vite' export { extraInlineDeps } from '../constants' @@ -20,7 +20,7 @@ export type { ConfigEnv, ViteUserConfig } * @deprecated Use `ViteUserConfig` instead */ export type UserConfig = ViteUserConfig -export type { UserProjectConfigExport, UserProjectConfigFn, UserWorkspaceConfig, WorkspaceProjectConfiguration } +export type { TestProjectConfiguration, UserProjectConfigExport, UserProjectConfigFn, UserWorkspaceConfig, WorkspaceProjectConfiguration } export type UserConfigFnObject = (env: ConfigEnv) => ViteUserConfig export type UserConfigFnPromise = (env: ConfigEnv) => Promise export type UserConfigFn = ( @@ -51,6 +51,6 @@ export function defineProject(config: UserProjectConfigExport): UserProjectConfi return config } -export function defineWorkspace(config: WorkspaceProjectConfiguration[]): WorkspaceProjectConfiguration[] { +export function defineWorkspace(config: TestProjectConfiguration[]): TestProjectConfiguration[] { return config } diff --git a/packages/vitest/src/utils/test-helpers.ts b/packages/vitest/src/utils/test-helpers.ts index 57b122c6fc35..2d9c44d994b1 100644 --- a/packages/vitest/src/utils/test-helpers.ts +++ b/packages/vitest/src/utils/test-helpers.ts @@ -32,7 +32,7 @@ export async function groupFilesByEnv( const filesWithEnv = await Promise.all( files.map(async (spec) => { const file = spec.moduleId - const project = spec.project.workspaceProject + const project = spec.project const code = await fs.readFile(file, 'utf-8') // 1. Check for control comments in the file From 6e2e986d31071559008459c69a98d176889a5bae Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 15:50:33 +0100 Subject: [PATCH 09/16] chore: remove `testProject` --- packages/vitest/src/node/project.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index 0931579ba840..65ce9c15016f 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -67,9 +67,10 @@ export class TestProject { * @deprecated use the current project instead */ public readonly workspaceProject: TestProject - /** @deprecated use the current project instead */ - public testProject: TestProject + /** + * Temporary directory for the project. This is unique for each project. Vitest stores transformed content here. + */ public readonly tmpDir = join(tmpdir(), nanoid()) vitenode!: ViteNodeServer @@ -93,7 +94,6 @@ export class TestProject { /** @deprecated */ public options?: InitializeProjectOptions, ) { - this.testProject = this this.vitest = vitest this.ctx = vitest this.workspaceProject = this @@ -554,7 +554,6 @@ export class TestProject { } this.closingPromise = undefined - this.testProject = this this._vite = server @@ -628,7 +627,6 @@ export class TestProject { vitest.config.provide[providedKey], ) } - project.testProject = project return project } From 74a2a5621dbd19c870dd8619912a2fc4048eff9e Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 15:51:07 +0100 Subject: [PATCH 10/16] chore: remove self reference --- packages/vitest/src/node/project.ts | 7 ------- test/cli/test/reported-tasks.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index 65ce9c15016f..714bef9eaeed 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -62,12 +62,6 @@ export class TestProject { /** @deprecated use `vitest` instead */ public ctx: Vitest - /** - * The workspace project this test project is associated with. - * @deprecated use the current project instead - */ - public readonly workspaceProject: TestProject - /** * Temporary directory for the project. This is unique for each project. Vitest stores transformed content here. */ @@ -96,7 +90,6 @@ export class TestProject { ) { this.vitest = vitest this.ctx = vitest - this.workspaceProject = this this.globalConfig = vitest.config } diff --git a/test/cli/test/reported-tasks.test.ts b/test/cli/test/reported-tasks.test.ts index 264b1e033b23..15b0b768ef84 100644 --- a/test/cli/test/reported-tasks.test.ts +++ b/test/cli/test/reported-tasks.test.ts @@ -35,7 +35,7 @@ beforeAll(async () => { logHeapUsage: true, }) state = ctx!.state - project = ctx!.getCoreWorkspaceProject() + project = ctx!.getRootTestProject() files = state.getFiles() expect(files).toHaveLength(1) testModule = state.getReportedEntity(files[0])! as TestModule @@ -55,7 +55,7 @@ it('correctly reports a file', () => { expect(testModule.id).toBe(files[0].id) expect(testModule.location).toBeUndefined() expect(testModule.moduleId).toBe(resolve(root, './1_first.test.ts')) - expect(testModule.project.workspaceProject).toBe(project) + expect(testModule.project).toBe(project) expect(testModule.children.size).toBe(14) const tests = [...testModule.children.tests()] From 5da03c0994f5fb856a065e7fbcdef84f8a25eb40 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 18:27:31 +0100 Subject: [PATCH 11/16] docs: add some docs --- docs/advanced/api.md | 131 +++++++++++++++++++++++++++++++++++++ docs/advanced/reporters.md | 49 -------------- 2 files changed, 131 insertions(+), 49 deletions(-) diff --git a/docs/advanced/api.md b/docs/advanced/api.md index 4d1fcecaa252..dba886cf8728 100644 --- a/docs/advanced/api.md +++ b/docs/advanced/api.md @@ -1,3 +1,7 @@ +--- +outline: [2, 3] +--- + # Node API ::: warning @@ -135,3 +139,130 @@ export default function setup({ provide }) { } ``` ::: + +## TestProject 2.2.0 + +- **Alias**: `WorkspaceProject` before 2.2.0 + +### name + +The name is a unique string assigned by the user or interpreted by Vitest. If user did not provide a name, Vitest tries to load a `package.json` in the root of the project and takes the `name` property from there. If there is no `package.json`, Vitest uses the name of the folder by default. Inline projects use numbers as the name (converted to string). + +::: code-group +```ts [node.js] +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +vitest.projects.map(p => p.name) === [ + '@pkg/server', + 'utils', + '2', + 'custom' +] +``` +```ts [vitest.workspace.js] +export default [ + './packages/server', // has package.json with "@pkg/server" + './utils', // doesn't have a package.json file + { + // doesn't customize the name + test: { + pool: 'node', + }, + }, + { + // customized the name + test: { + name: 'custom', + }, + }, +] +``` +::: + +### vitest + +`vitest` references the global [`vitest`](#vitest) process. + +### serializedConfig + +This is the test config that all tests will receive. Vitest [serializes config](https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/config/serializeConfig.ts) manually by removing all functions and properties that are not possible to serialize. Since this value is available in both tests and node, it is exported from the main entry point. + +```ts +import type { SerializedConfig } from 'vitest' + +const config: SerializedConfig = vitest.projects[0].serializedConfig +``` + +### globalConfig + +The test config that `vitest` was initialized with. If this is the root project, `globalConfig` and `config` will reference the same object. This config is useful for values that cannot be set on the project level, like `coverage` or `reporters`. + +```ts +import type { ResolvedConfig } from 'vitest/node' + +vitest.config === vitest.projects[0].globalConfig +``` + +### config + +This is the project's resolved test config. + +### vite + +This is project's `ViteDevServer`. All projects have their own Vite servers. + +### browser + +This value will be set only if tests are running in the browser. If `browser` is enabled, but tests didn't run yet, this will be `undefined`. If you need to check if the project supports browser tests, use `project.isBrowserSupported()` method. + +::: warning +The browser API is even more experimental and doesn't follow SemVer. The browser API will be standardized separately from the rest of the APIs. +::: + +### provide + +A way to provide custom values to tests in addition to [`config.provide`](/config/#provide) field. + +::: code-group +```ts [node.js] +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +const project = vitest.projects.find(p => p.name === 'custom') +project.provide('key', 'value') +await vitest.start() +``` +```ts [test.spec.js] +import { inject } from 'vitest' +const value = inject('key') +``` +::: + +The values can be provided dynamicaly. Provided value in tests will be updated on their next run. + +### getProvidedContext + +This returns the context object. Every project also inherits the global context set by `vitest.provide`. + +```ts +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +vitest.provide('global', true) +const project = vitest.projects.find(p => p.name === 'custom') +project.provide('key', 'value') + +// { global: true, key: 'value' } +const context = project.getProvidedContext() +``` + +::: tip +Project context values will always override global ones. +::: + +### createSpecification +### isRootProject +### globTestFiles +### matchesTestGlob +### close diff --git a/docs/advanced/reporters.md b/docs/advanced/reporters.md index d9dd0e1da6a0..9f06512ce7b9 100644 --- a/docs/advanced/reporters.md +++ b/docs/advanced/reporters.md @@ -384,55 +384,6 @@ function onFileCollected(testModule: TestModule): void { } ``` -### TestProject - -`TestProject` is a project assosiated with the module. Every test and suite inside that module will reference the same project. - -Project is useful to get the configuration or provided context. - -```ts -declare class TestProject { - /** - * The global vitest instance. - * @experimental The public Vitest API is experimental and does not follow semver. - */ - readonly vitest: Vitest - /** - * The workspace project this test project is associated with. - * @experimental The public Vitest API is experimental and does not follow semver. - */ - readonly workspaceProject: WorkspaceProject - /** - * Vite's dev server instance. Every workspace project has its own server. - */ - readonly vite: ViteDevServer - /** - * Resolved project configuration. - */ - readonly config: ResolvedProjectConfig - /** - * Resolved global configuration. If there are no workspace projects, this will be the same as `config`. - */ - readonly globalConfig: ResolvedConfig - /** - * Serialized project configuration. This is the config that tests receive. - */ - get serializedConfig(): SerializedConfig - /** - * The name of the project or an empty string if not set. - */ - name(): string - /** - * Custom context provided to the project. - */ - context(): ProvidedContext - /** - * Provide a custom serializable context to the project. This context will be available for tests once they run. - */ - provide(key: T, value: ProvidedContext[T]): void -} -``` - ## Exported Reporters `vitest` comes with a few [built-in reporters](/guide/reporters) that you can use out of the box. From e5e066aa7931dabc6114aaa183201b93433999af Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 15 Nov 2024 11:36:52 +0100 Subject: [PATCH 12/16] docs: more info --- docs/advanced/api.md | 86 +++++++++++++++++++++++++++-- docs/advanced/pool.md | 6 +- packages/vitest/src/node/core.ts | 23 +++----- packages/vitest/src/node/project.ts | 15 +++-- 4 files changed, 99 insertions(+), 31 deletions(-) diff --git a/docs/advanced/api.md b/docs/advanced/api.md index dba886cf8728..56eb56346a2a 100644 --- a/docs/advanced/api.md +++ b/docs/advanced/api.md @@ -100,7 +100,7 @@ You can start running tests or benchmarks with `start` method. You can pass an a ### `provide` -Vitest exposes `provide` method which is a shorthand for `vitest.getCoreWorkspaceProject().provide`. With this method you can pass down values from the main thread to tests. All values are checked with `structuredClone` before they are stored, but the values themselves are not cloned. +Vitest exposes `provide` method which is a shorthand for `vitest.getRootTestProject().provide`. With this method you can pass down values from the main thread to tests. All values are checked with `structuredClone` before they are stored, but the values themselves are not cloned. To recieve the values in the test, you need to import `inject` method from `vitest` entrypont: @@ -127,11 +127,11 @@ declare module 'vitest' { ``` ::: warning -Technically, `provide` is a method of `WorkspaceProject`, so it is limited to the specific project. However, all projects inherit the values from the core project which makes `vitest.provide` universal way of passing down values to tests. +Technically, `provide` is a method of [`TestProject`](#testproject), so it is limited to the specific project. However, all projects inherit the values from the core project which makes `vitest.provide` universal way of passing down values to tests. ::: ::: tip -This method is also available to [global setup files](/config/#globalsetup) for cases where you don't want to use the public API: +This method is also available to [global setup files](/config/#globalsetup) for cases where you cannot use the public API: ```js export default function setup({ provide }) { @@ -222,7 +222,7 @@ The browser API is even more experimental and doesn't follow SemVer. The browser ### provide -A way to provide custom values to tests in addition to [`config.provide`](/config/#provide) field. +A way to provide custom values to tests in addition to [`config.provide`](/config/#provide) field. All values are validated with `structuredClone` before they are stored, but the values on `providedContext` themselves are not cloned. ::: code-group ```ts [node.js] @@ -262,7 +262,85 @@ Project context values will always override global ones. ::: ### createSpecification + +Create a test specification that can be used in `vitest.runFiles`. Specification scopes the test file to a specific `project` and `pool` (optionally). + +```ts +import { createVitest } from 'vitest/node' +import { resolve } from 'node:path/posix' + +const vitest = await createVitest('test') +const project = vitest.projects[0] +const specification = project.createSpecification( + resolve('./basic.test.ts'), + 'threads', // optional override +) +await vitest.runFiles([specification], true) +``` + +::: warning +`createSpecification` expects an absolute file path. It doesn't resolve the file or check that it exists on the file system. +::: + ### isRootProject + +Checks if the current project is the root project. You can also get the root project by calling `vitest.getRootTestProject()`. + +The root project generally doesn't run any tests and is not included in `vitest.projects` unless the user explicitly includes the root config in their workspace. + +The primary goal of the root project is to setup the global config. In fact, `rootProject.config` references `rootProject.globalConfig` and `vitest.config` directly. + ### globTestFiles + +Globs all test files. This function returns an object with regular tests and typecheck tests: + +```ts +interface GlobReturn { + /** + * Test files that match the filters. + */ + testFiles: string[] + /** + * Typecheck test files that match the filters. This will be empty unless `typecheck.enabled` is `true`. + */ + typecheckTestFiles: string[] +} +``` + +::: tip +Vitest uses [fast-glob](https://www.npmjs.com/package/fast-glob) to find test files. `test.dir`, `test.root`, `root` or `process.cwd()` define the `cwd` option. + +This method looks at several config options: + +- `test.include`, `test.exclude` to find regular test files +- `test.includeSource`, `test.exclude` to find in-source tests +- `test.typecheck.include`, `test.typecheck.exclude` to find typecheck tests +::: + ### matchesTestGlob + +This method checks if the file is a regular test file. It uses the same config properties that `globTestFiles` uses for validation. + +This method also accepts a second parameter, which is the source code. This is used to validate if the file is an in-source test. If you are calling this method several times for several projects it is recommended to read the file once and pass it down directly. + +```ts +import { createVitest } from 'vitest/node' +import { resolve } from 'node:path/posix' + +const vitest = await createVitest('test') +const project = vitest.projects[0] + +project.matchesTestGlob(resolve('./basic.test.ts')) // true +project.matchesTestGlob(resolve('./basic.ts')) // false +project.matchesTestGlob(resolve('./basic.ts'), ` +if (import.meta.vitest) { + // ... +} +`) // true if `includeSource` is set +``` + ### close + +Closes the project and all associated resources. This can only be called once; the closing promise is cached until the server restarts. If the resources are needed again, create a new project. + +In detail, this method closes the Vite server, stops the typechecker service, closes the browser if it's running, deletes the temporary directory that holds the source code, and resets the provided context. diff --git a/docs/advanced/pool.md b/docs/advanced/pool.md index 2f7368dbfcd9..f9f52889298e 100644 --- a/docs/advanced/pool.md +++ b/docs/advanced/pool.md @@ -40,12 +40,12 @@ export default defineConfig({ The file specified in `pool` option should export a function (can be async) that accepts `Vitest` interface as its first option. This function needs to return an object matching `ProcessPool` interface: ```ts -import { ProcessPool, WorkspaceProject } from 'vitest/node' +import { ProcessPool, TestSpecification } from 'vitest/node' export interface ProcessPool { name: string - runTests: (files: [project: WorkspaceProject, testFile: string][], invalidates?: string[]) => Promise - collectTests: (files: [project: WorkspaceProject, testFile: string][], invalidates?: string[]) => Promise + runTests: (files: TestSpecification[], invalidates?: string[]) => Promise + collectTests: (files: TestSpecification[], invalidates?: string[]) => Promise close?: () => Promise } ``` diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 5d6b411a1ef6..38affd4a98af 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -193,7 +193,7 @@ export class Vitest { ) } if (!this.coreWorkspaceProject) { - this.coreWorkspaceProject = TestProject.createBasicProject(this) + this.coreWorkspaceProject = TestProject._createBasicProject(this) } if (this.config.testNamePattern) { @@ -207,27 +207,18 @@ export class Vitest { this.getRootTestProject().provide(key, value) } - /** - * @deprecated internal, use `_createCoreProject` instead - */ - createCoreProject() { - return this._createCoreProject() - } - /** * @internal */ _createCoreProject() { - this.coreWorkspaceProject = TestProject.createBasicProject(this) - return this.coreWorkspaceProject - } - - /** @deprecated use `getRootTestProject` */ - public getCoreWorkspaceProject(): TestProject { + this.coreWorkspaceProject = TestProject._createBasicProject(this) return this.coreWorkspaceProject } public getRootTestProject(): TestProject { + if (!this.coreWorkspaceProject) { + throw new Error(`Root project is not initialized. This means that the Vite server was not established yet and the the workspace config is not resolved.`) + } return this.coreWorkspaceProject } @@ -1033,11 +1024,11 @@ export class Vitest { teardownProjects.push(this.coreWorkspaceProject) } // do teardown before closing the server - for await (const project of teardownProjects.reverse()) { + for (const project of teardownProjects.reverse()) { await project._teardownGlobalSetup() } - const closePromises: unknown[] = this.resolvedProjects.map(w => w.close().then(() => (w as any)._vite = undefined as any)) + const closePromises: unknown[] = this.resolvedProjects.map(w => w.close()) // close the core workspace server only once // it's possible that it's not initialized at all because it's not running any tests if (!this.resolvedProjects.includes(this.coreWorkspaceProject)) { diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index 714bef9eaeed..2cee7c96ce2d 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -499,7 +499,7 @@ export class TestProject { } /** - * Closes the project and all associated resources. This will only be called once. + * Closes the project and all associated resources. This can only be called once; the closing promise is cached until the server restarts. * If the resources are needed again, create a new project. */ public close(): Promise { @@ -511,7 +511,10 @@ export class TestProject { this.browser?.close(), this.clearTmpDir(), ].filter(Boolean), - ).then(() => (this._provided = {} as any)) + ).then(() => { + this._provided = {} as any + this._vite = undefined + }) } return this.closingPromise } @@ -603,7 +606,8 @@ export class TestProject { await this.browser?.initBrowserProvider() } - static createBasicProject(vitest: Vitest): TestProject { + /** @internal */ + static _createBasicProject(vitest: Vitest): TestProject { const project = new TestProject( vitest.config.name || vitest.config.root, vitest, @@ -622,11 +626,6 @@ export class TestProject { } return project } - - /** @deprecated */ - static async createCoreProject(vitest: Vitest): Promise { - return TestProject.createBasicProject(vitest) - } } export { From c92ed20e70cdb848326765115d3d7da53825a50e Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 15 Nov 2024 12:19:08 +0100 Subject: [PATCH 13/16] chore: fix access --- packages/vitest/src/node/core.ts | 3 ++- packages/vitest/src/node/logger.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 38affd4a98af..e3ab3ebc5a35 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -75,7 +75,8 @@ export class Vitest { public packageInstaller: VitestPackageInstaller - private coreWorkspaceProject!: TestProject + /** @internal */ + public coreWorkspaceProject!: TestProject /** @private */ public resolvedProjects: TestProject[] = [] diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index 391b291a97d6..05cd874d0a85 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -105,7 +105,7 @@ export class Logger { printError(err: unknown, options: ErrorOptions = {}): PrintErrorResult | undefined { const { fullStack = false, type } = options const project = options.project - ?? this.ctx.getRootTestProject() + ?? this.ctx.coreWorkspaceProject ?? this.ctx.projects[0] return printError(err, project, { type, From 98367b7e27553d0713f502b36885cf6cac1ec912 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 15 Nov 2024 12:35:16 +0100 Subject: [PATCH 14/16] chore: fix getRootTestProject access --- packages/ui/node/reporter.ts | 2 +- test/coverage-test/test/threshold-100.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/node/reporter.ts b/packages/ui/node/reporter.ts index 002ea60751ce..696c13e7e0b4 100644 --- a/packages/ui/node/reporter.ts +++ b/packages/ui/node/reporter.ts @@ -63,7 +63,7 @@ export default class HTMLReporter implements Reporter { const result: HTMLReportData = { paths: this.ctx.state.getPaths(), files: this.ctx.state.getFiles(), - config: this.ctx.getCoreWorkspaceProject().getSerializableConfig(), + config: this.ctx.getRootTestProject().serializedConfig, unhandledErrors: this.ctx.state.getUnhandledErrors(), moduleGraph: {}, sources: {}, diff --git a/test/coverage-test/test/threshold-100.test.ts b/test/coverage-test/test/threshold-100.test.ts index 8c5a3e261cab..9edf7ae7efaa 100644 --- a/test/coverage-test/test/threshold-100.test.ts +++ b/test/coverage-test/test/threshold-100.test.ts @@ -20,7 +20,7 @@ test('{ threshold: { 100: true }}', async () => { 'verbose', { onInit(ctx) { - ctx.getCoreWorkspaceProject().provide('coverage', { + ctx.getRootTestProject().provide('coverage', { provider: ctx.config.coverage.provider, thresholds: (ctx.config.coverage as any).thresholds, }) From 1b40e924019ad32cfaffa10295cea29f148d06e5 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 15 Nov 2024 16:26:11 +0100 Subject: [PATCH 15/16] feat: add state.getTestModules --- docs/.vitepress/config.ts | 51 +++++++++++------- docs/advanced/guide/tests.md | 69 +++++++++++++++++++++++++ docs/advanced/reporters.md | 2 - packages/vitest/src/node/cli/cli-api.ts | 2 +- packages/vitest/src/node/create.ts | 2 +- packages/vitest/src/node/state.ts | 4 ++ 6 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 docs/advanced/guide/tests.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 929c93b49c19..532ceb4c467b 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -216,24 +216,39 @@ export default ({ mode }: { mode: string }) => { { items: [ { - text: 'Vitest Node API', - link: '/advanced/api', - }, - { - text: 'Runner API', - link: '/advanced/runner', - }, - { - text: 'Task Metadata', - link: '/advanced/metadata', - }, - { - text: 'Extending Reporters', - link: '/advanced/reporters', - }, - { - text: 'Custom Pool', - link: '/advanced/pool', + text: 'API', + items: [ + + { + text: 'Vitest Node API', + link: '/advanced/api', + }, + { + text: 'Runner API', + link: '/advanced/runner', + }, + { + text: 'Task Metadata', + link: '/advanced/metadata', + }, + ], + }, + { + text: 'Guides', + items: [ + { + text: 'Running Tests', + link: '/advanced/guide/tests', + }, + { + text: 'Extending Reporters', + link: '/advanced/reporters', + }, + { + text: 'Custom Pool', + link: '/advanced/pool', + }, + ], }, ], }, diff --git a/docs/advanced/guide/tests.md b/docs/advanced/guide/tests.md new file mode 100644 index 000000000000..ce768f7815f3 --- /dev/null +++ b/docs/advanced/guide/tests.md @@ -0,0 +1,69 @@ +# Running Tests + +::: warning +This guide explains how to use the advanced API to run tests via a Node.js script. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. + +Breaking changes might not follow SemVer, please pin Vitest's version when using the experimental API. +::: + +Vitest exposes two methods to initiate Vitest: + +- `startVitest` initiates Vitest, validates the packages are installed and runs tests immidiatly +- `createVitest` only initiates Vitest and doesn't run any tests + +## `startVitest` + +```ts +import { startVitest } from 'vitest/node' + +const vitest = await startVitest( + 'test', + [], // CLI filters + {}, // override test config + {}, // override Vite config + {}, // custom Vitest options +) +const testModules = vitest.state.getTestModules() +for (const testModule of testModules) { + console.log(testModule.moduleId, 'results', testModule.result()) +} +``` + +::: tip +[`TestModule`](/advanced/reporters#TestModule), [`TestSuite`](/advanced/reporters#TestSuite) and [`TestCase`](/advanced/reporters#TestCase) APIs are not experimental and follow SemVer since Vitest 2.1. +::: + +## `createVitest` + +`createVitest` method doesn't validate that required packages are installed. This method also doesn't respect `config.standalone` or `config.mergeReports`. Vitest also won't be closed automatically even if `watch` is disabled. + +```ts +import { createVitest } from 'vitest/node' + +const vitest = await createVitest( + 'test', + {}, // override test config + {}, // override Vite config + {}, // custom Vitest options +) + +// called when `vitest.cancelCurrentRun()` is invoked +vitest.onCancel(() => {}) +// called during `vitest.close()` call +vitest.onClose(() => {}) +// called when Vitest reruns test files +vitest.onTestsRerun((files) => {}) + +try { + // this will set process.exitCode to 1 if tests failed + await vitest.start(['my-filter']) +} +catch (err) { + // this can throw + // "FilesNotFoundError" if no files were found + // "GitNotFoundError" if `--changed` is enabled and repository is not initialized +} +finally { + await vitest.close() +} +``` diff --git a/docs/advanced/reporters.md b/docs/advanced/reporters.md index 9f06512ce7b9..6f3236632cd0 100644 --- a/docs/advanced/reporters.md +++ b/docs/advanced/reporters.md @@ -87,8 +87,6 @@ class MyReporter implements Reporter { } } ``` - -We are planning to stabilize this API in Vitest 2.1. ::: ### TestCase diff --git a/packages/vitest/src/node/cli/cli-api.ts b/packages/vitest/src/node/cli/cli-api.ts index 57ce175cc310..e36d07432768 100644 --- a/packages/vitest/src/node/cli/cli-api.ts +++ b/packages/vitest/src/node/cli/cli-api.ts @@ -42,7 +42,7 @@ export async function startVitest( options: CliOptions = {}, viteOverrides?: ViteUserConfig, vitestOptions?: VitestOptions, -): Promise { +): Promise { const root = resolve(options.root || process.cwd()) const ctx = await prepareVitest( diff --git a/packages/vitest/src/node/create.ts b/packages/vitest/src/node/create.ts index cf64cfaedd52..8d0fe76a8949 100644 --- a/packages/vitest/src/node/create.ts +++ b/packages/vitest/src/node/create.ts @@ -18,7 +18,7 @@ export async function createVitest( options: UserConfig, viteOverrides: ViteUserConfig = {}, vitestOptions: VitestOptions = {}, -) { +): Promise { const ctx = new Vitest(mode, vitestOptions) const root = slash(resolve(options.root || process.cwd())) diff --git a/packages/vitest/src/node/state.ts b/packages/vitest/src/node/state.ts index 3630d821e09c..6903b6084f85 100644 --- a/packages/vitest/src/node/state.ts +++ b/packages/vitest/src/node/state.ts @@ -90,6 +90,10 @@ export class StateManager { }) } + getTestModules(keys?: string[]): TestModule[] { + return this.getFiles(keys).map(file => this.getReportedEntity(file) as TestModule) + } + getFilepaths(): string[] { return Array.from(this.filesMap.keys()) } From 45093753670faf94196bf3025a9c09f5af7e8fdc Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 18 Nov 2024 10:24:05 +0100 Subject: [PATCH 16/16] chore: cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ari Perkkiƶ --- docs/advanced/api.md | 4 ++-- packages/vitest/src/node/core.ts | 2 +- packages/vitest/src/node/logger.ts | 2 +- packages/vitest/src/node/project.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/advanced/api.md b/docs/advanced/api.md index 56eb56346a2a..2c3959fb6ade 100644 --- a/docs/advanced/api.md +++ b/docs/advanced/api.md @@ -167,7 +167,7 @@ export default [ { // doesn't customize the name test: { - pool: 'node', + pool: 'threads', }, }, { @@ -222,7 +222,7 @@ The browser API is even more experimental and doesn't follow SemVer. The browser ### provide -A way to provide custom values to tests in addition to [`config.provide`](/config/#provide) field. All values are validated with `structuredClone` before they are stored, but the values on `providedContext` themselves are not cloned. +A way to provide custom values to tests in addition to [`config.provide`](/config/#provide) field. All values are validated with [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) before they are stored, but the values on `providedContext` themselves are not cloned. ::: code-group ```ts [node.js] diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index e3ab3ebc5a35..712690acd626 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -236,7 +236,7 @@ export class Vitest { public getProjectByName(name: string = '') { return this.projects.find(p => p.name === name) - || this.getRootTestProject() + || this.coreWorkspaceProject || this.projects[0] } diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index 05cd874d0a85..fd3ec64292e9 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -163,7 +163,7 @@ export class Logger { } this.ctx.projects.forEach((project) => { const config = project.config - const output = project.isRootProject() || !project.name ? '' : `[${project.name}]` + const output = (project.isRootProject() || !project.name) ? '' : `[${project.name}]` if (output) { this.console.error(c.bgCyan(`${output} Config`)) } diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index 2cee7c96ce2d..1b4ca34363c8 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -111,7 +111,7 @@ export class TestProject { }, ) } - // casting `any` because the devault type is `never` since `ProvidedContext` is empty + // casting `any` because the default type is `never` since `ProvidedContext` is empty (this._provided as any)[key] = value } @@ -190,7 +190,7 @@ export class TestProject { } /** - * Is this the root project. The root project is the one that has the root config. + * Check if this is the root project. The root project is the one that has the root config. */ public isRootProject(): boolean { return this.vitest.getRootTestProject() === this