diff --git a/docs/guide/cli.md b/docs/guide/cli.md index ebd67860a6fe..c831539f5a83 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -100,6 +100,7 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim | `--typecheck [options]` | Custom options for typecheck pool. If passed without options, enables typechecking | | `--typecheck.enabled` | Enable typechecking alongside tests (default: `false`) | | `--typecheck.only` | Run only typecheck tests. This automatically enables typecheck (default: `false`) | +| `--project` | The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: `--project=1 --project=2` | | `-h, --help` | Display available CLI options | ::: tip diff --git a/docs/guide/index.md b/docs/guide/index.md index 4cd1d78bf42c..fadddb5b8c77 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -19,18 +19,18 @@ You can try Vitest online on [StackBlitz](https://vitest.new). It runs Vitest di Learn how to install by Video ::: code-group - ```bash [npm] - npm install -D vitest - ``` - ```bash [yarn] - yarn add -D vitest - ``` - ```bash [pnpm] - pnpm add -D vitest - ``` - ```bash [bun] - bun add -D vitest - ``` +```bash [npm] +npm install -D vitest +``` +```bash [yarn] +yarn add -D vitest +``` +```bash [pnpm] +pnpm add -D vitest +``` +```bash [bun] +bun add -D vitest +``` ::: :::tip diff --git a/docs/guide/workspace.md b/docs/guide/workspace.md index 9212ebe46f3d..2df427dcd2c4 100644 --- a/docs/guide/workspace.md +++ b/docs/guide/workspace.md @@ -99,6 +99,49 @@ export default defineProject({ ``` ::: +## Running tests + +To run tests inside the workspace, define a script in your root `package.json`: + +```json +{ + "scripts": { + "test": "vitest" + } +} +``` + +Now tests can be run using your package manager: + +::: code-group +```bash [npm] +npm run test +``` +```bash [yarn] +yarn test +``` +```bash [pnpm] +pnpm run test +``` +```bash [bun] +bun test +``` +::: + +If you need to run tests only inside a single project, use the `--project` CLI option: + +```bash +npm run test --project e2e +``` + +::: tip +CLI option `--project` can be used multiple times to filter out several projects: + +```bash +npm run test --project e2e --project unit +``` +::: + ## Configuration None of the configuration options are inherited from the root-level config file. You can create a shared config file and merge it with the project config yourself: diff --git a/packages/vitest/src/node/cli.ts b/packages/vitest/src/node/cli.ts index 415959c5ffbe..49bc4a5cbf5b 100644 --- a/packages/vitest/src/node/cli.ts +++ b/packages/vitest/src/node/cli.ts @@ -60,6 +60,7 @@ cli .option('--typecheck [options]', 'Custom options for typecheck pool') .option('--typecheck.enabled', 'Enable typechecking alongside tests (default: false)') .option('--typecheck.only', 'Run only typecheck tests. This automatically enables typecheck (default: false)') + .option('--project ', 'The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: --project=1 --project=2') .help() cli diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 8b605142885e..bc36be4c30d3 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -56,6 +56,7 @@ export class Vitest { private coreWorkspaceProject!: WorkspaceProject + private resolvedProjects: WorkspaceProject[] = [] public projects: WorkspaceProject[] = [] private projectsTestFiles = new Map>() @@ -151,7 +152,12 @@ export class Vitest { await Promise.all(this._onSetServer.map(fn => fn())) - this.projects = await this.resolveWorkspace(cliOptions) + const projects = await this.resolveWorkspace(cliOptions) + this.projects = projects + this.resolvedProjects = projects + const filteredProjects = toArray(resolved.project) + if (filteredProjects.length) + this.projects = this.projects.filter(p => filteredProjects.includes(p.getName())) if (!this.coreWorkspaceProject) this.coreWorkspaceProject = WorkspaceProject.createBasicProject(this) @@ -512,6 +518,17 @@ export class Vitest { await this.report('onWatcherStart', this.state.getFiles(files)) } + async changeProjectName(pattern: string) { + if (pattern === '') + delete this.configOverride.project + else + this.configOverride.project = pattern + + this.projects = this.resolvedProjects.filter(p => p.getName() === pattern) + const files = (await this.globTestFiles()).map(([, file]) => file) + await this.rerunFiles(files, 'change project filter') + } + async changeNamePattern(pattern: string, files: string[] = this.state.getFilepaths(), trigger?: string) { // Empty test name pattern should reset filename pattern as well if (pattern === '') diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index bb40205bb42e..8a3e736f1726 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -3,6 +3,7 @@ import c from 'picocolors' import { version } from '../../../../package.json' import type { ErrorWithDiff } from '../types' import type { TypeCheckError } from '../typecheck/typechecker' +import { toArray } from '../utils' import { divider } from './reporters/renderers/utils' import { RandomSequencer } from './sequencers/RandomSequencer' import type { Vitest } from './core' @@ -95,6 +96,9 @@ export class Logger { const comma = c.dim(', ') if (filters?.length) this.console.error(c.dim('filter: ') + c.yellow(filters.join(comma))) + const projectsFilter = toArray(config.project) + if (projectsFilter.length) + this.console.error(c.dim('projects: ') + c.yellow(projectsFilter.join(comma))) this.ctx.projects.forEach((project) => { const config = project.config const name = project.getName() diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index 73a1c754b710..bde767865e39 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -1,7 +1,7 @@ import { performance } from 'node:perf_hooks' import c from 'picocolors' import type { ErrorWithDiff, File, Reporter, Task, TaskResultPack, UserConsoleLog } from '../../types' -import { getFullName, getSafeTimers, getSuites, getTests, hasFailed, hasFailedSnapshot, isCI, isNode, relativePath } from '../../utils' +import { getFullName, getSafeTimers, getSuites, getTests, hasFailed, hasFailedSnapshot, isCI, isNode, relativePath, toArray } from '../../utils' import type { Vitest } from '../../node' import { F_RIGHT } from '../../utils/figures' import { UNKNOWN_TEST_ID } from '../../runtime/console' @@ -169,16 +169,17 @@ export abstract class BaseReporter implements Reporter { const TRIGGER = trigger ? c.dim(` ${this.relative(trigger)}`) : '' const FILENAME_PATTERN = this.ctx.filenamePattern ? `${BADGE_PADDING} ${c.dim('Filename pattern: ')}${c.blue(this.ctx.filenamePattern)}\n` : '' const TESTNAME_PATTERN = this.ctx.configOverride.testNamePattern ? `${BADGE_PADDING} ${c.dim('Test name pattern: ')}${c.blue(String(this.ctx.configOverride.testNamePattern))}\n` : '' + const PROJECT_FILTER = this.ctx.configOverride.project ? `${BADGE_PADDING} ${c.dim('Project name: ')}${c.blue(toArray(this.ctx.configOverride.project).join(', '))}\n` : '' - if (files.length > 1) { + if (files.length > 1 || !files.length) { // we need to figure out how to handle rerun all from stdin - this.ctx.logger.clearFullScreen(`\n${BADGE}${TRIGGER}\n${FILENAME_PATTERN}${TESTNAME_PATTERN}`) + this.ctx.logger.clearFullScreen(`\n${BADGE}${TRIGGER}\n${PROJECT_FILTER}${FILENAME_PATTERN}${TESTNAME_PATTERN}`) this._lastRunCount = 0 } else if (files.length === 1) { const rerun = this._filesInWatchMode.get(files[0]) ?? 1 this._lastRunCount = rerun - this.ctx.logger.clearFullScreen(`\n${BADGE}${TRIGGER} ${c.blue(`x${rerun}`)}\n${FILENAME_PATTERN}${TESTNAME_PATTERN}`) + this.ctx.logger.clearFullScreen(`\n${BADGE}${TRIGGER} ${c.blue(`x${rerun}`)}\n${PROJECT_FILTER}${FILENAME_PATTERN}${TESTNAME_PATTERN}`) } this._timeStart = new Date() diff --git a/packages/vitest/src/node/stdin.ts b/packages/vitest/src/node/stdin.ts index 636960092aa5..a9c4a3129edb 100644 --- a/packages/vitest/src/node/stdin.ts +++ b/packages/vitest/src/node/stdin.ts @@ -2,6 +2,7 @@ import readline from 'node:readline' import c from 'picocolors' import prompt from 'prompts' import { isWindows, stdout } from '../utils' +import { toArray } from '../utils/base' import type { Vitest } from './core' const keys = [ @@ -11,6 +12,7 @@ const keys = [ ['u', 'update snapshot'], ['p', 'filter by a filename'], ['t', 'filter by a test name regex pattern'], + ['w', 'filter by a project name'], ['q', 'quit'], ] const cancelKeys = ['space', 'c', 'h', ...keys.map(key => key[0]).flat()] @@ -76,6 +78,9 @@ export function registerConsoleShortcuts(ctx: Vitest) { // rerun only failed tests if (name === 'f') return ctx.rerunFailed() + // change project filter + if (name === 'w') + return inputProjectName() // change testNamePattern if (name === 't') return inputNamePattern() @@ -100,6 +105,18 @@ export function registerConsoleShortcuts(ctx: Vitest) { await ctx.changeNamePattern(filter.trim(), undefined, 'change pattern') } + async function inputProjectName() { + off() + const { filter = '' }: { filter: string } = await prompt([{ + name: 'filter', + type: 'text', + message: 'Input a single project name', + initial: toArray(ctx.configOverride.project)[0] || '', + }]) + on() + await ctx.changeProjectName(filter.trim()) + } + async function inputFilePattern() { off() const { filter = '' }: { filter: string } = await prompt([{ diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index aaf2c210a585..df434c519bb1 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -716,6 +716,11 @@ export interface UserConfig extends InlineConfig { * @example --shard=2/3 */ shard?: string + + /** + * Name of the project or projects to run. + */ + project?: string | string[] } export interface ResolvedConfig extends Omit, 'config' | 'filters' | 'browser' | 'coverage' | 'testNamePattern' | 'related' | 'api' | 'reporters' | 'resolveSnapshotPath' | 'benchmark' | 'shard' | 'cache' | 'sequence' | 'typecheck' | 'runner' | 'poolOptions' | 'pool'> {