From 67d93eda66c73b49af210ad8a117ecfceaa81a3d Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 19 Dec 2023 11:34:11 +0100 Subject: [PATCH] feat: add --workspace option, fix root resolution in workspaces (#4773) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ari Perkkiƶ --- docs/config/index.md | 9 ++++ docs/guide/cli.md | 2 +- packages/vitest/src/node/cli.ts | 6 +++ packages/vitest/src/node/config.ts | 49 +++++++++---------- packages/vitest/src/node/core.ts | 20 ++++++-- packages/vitest/src/node/plugins/workspace.ts | 4 +- packages/vitest/src/node/workspace.ts | 6 ++- packages/vitest/src/types/config.ts | 5 ++ .../fixtures/workspace/nested/e2e.projects.js | 1 + .../workspace/project-1/calculator-1.test.ts | 5 ++ .../workspace/project-2/calculator-2.test.ts | 5 ++ test/config/test/workspace.test.ts | 9 ++++ test/workspaces/vitest.workspace.ts | 2 +- 13 files changed, 87 insertions(+), 36 deletions(-) create mode 100644 test/config/fixtures/workspace/nested/e2e.projects.js create mode 100644 test/config/fixtures/workspace/project-1/calculator-1.test.ts create mode 100644 test/config/fixtures/workspace/project-2/calculator-2.test.ts create mode 100644 test/config/test/workspace.test.ts diff --git a/docs/config/index.md b/docs/config/index.md index 597bc3e4003c..7151d1c3b311 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -2028,3 +2028,12 @@ Relevant only when using with `shouldAdvanceTime: true`. increment mocked time b - **Default:** `false` Tells fake timers to clear "native" (i.e. not fake) timers by delegating to their respective handlers. These are not cleared by default, leading to potentially unexpected behavior if timers existed prior to starting fake timers session. + +### workspace + +- **Type:** `string` +- **CLI:** `--workspace=./file.js` +- **Default:** `vitest.{workspace,projects}.{js,ts,json}` close to the config file or root +- **Version:** Since Vitest 1.1.0 + +Path to a [workspace](/guide/workspace) config file relative to [root](#root). diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 66a0661c4d3a..0a0c95f0bcb9 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -78,8 +78,8 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim | `--outputFile ` | Write test results to a file when the `--reporter=json` or `--reporter=junit` option is also specified
Via [cac's dot notation] you can specify individual outputs for multiple reporters | | `--coverage` | Enable coverage report | | `--run` | Do not watch | -| `--mode` | Override Vite mode (default: `test`) | | `--mode ` | Override Vite mode (default: `test`) | +| `--workspace ` | Path to a workspace configuration file | | `--globals` | Inject APIs globally | | `--dom` | Mock browser API with happy-dom | | `--browser [options]` | Run tests in [the browser](/guide/browser) (default: `false`) | diff --git a/packages/vitest/src/node/cli.ts b/packages/vitest/src/node/cli.ts index 598fc07f53ed..d36f3b77eda6 100644 --- a/packages/vitest/src/node/cli.ts +++ b/packages/vitest/src/node/cli.ts @@ -29,6 +29,7 @@ cli .option('--coverage.all', 'Whether to include all files, including the untested ones into report', { default: true }) .option('--run', 'Disable watch mode') .option('--mode ', 'Override Vite mode (default: test)') + .option('--workspace ', 'Path to a workspace configuration file') .option('--globals', 'Inject apis globally') .option('--dom', 'Mock browser API with happy-dom') .option('--browser [options]', 'Run tests in the browser (default: false)') @@ -150,6 +151,11 @@ function normalizeCliOptions(argv: CliOptions): CliOptions { else delete argv.config + if (argv.workspace) + argv.workspace = normalize(argv.workspace) + else + delete argv.workspace + if (argv.dir) argv.dir = normalize(argv.dir) else diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 674b80dc1d0e..0f99bfd4cf8c 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -22,6 +22,13 @@ const extraInlineDeps = [ '@nuxt/test-utils', ] +function resolvePath(path: string, root: string) { + return normalize( + resolveModule(path, { paths: [root] }) + ?? resolve(root, path), + ) +} + export function resolveApiServerConfig( options: Options, ): ApiConfig | undefined { @@ -193,10 +200,8 @@ export function resolveConfig( resolved.server.deps.moduleDirectories ??= [] resolved.server.deps.moduleDirectories.push(...resolved.deps.moduleDirectories) - if (resolved.runner) { - resolved.runner = resolveModule(resolved.runner, { paths: [resolved.root] }) - ?? resolve(resolved.root, resolved.runner) - } + if (resolved.runner) + resolved.runner = resolvePath(resolved.runner, resolved.root) resolved.testNamePattern = resolved.testNamePattern ? resolved.testNamePattern instanceof RegExp @@ -274,19 +279,18 @@ export function resolveConfig( } } - if (!builtinPools.includes(resolved.pool as BuiltinPool)) { - resolved.pool = normalize( - resolveModule(resolved.pool, { paths: [resolved.root] }) - ?? resolve(resolved.root, resolved.pool), - ) + if (resolved.workspace) { + // if passed down from the CLI and it's relative, resolve relative to CWD + resolved.workspace = options.workspace && options.workspace[0] === '.' + ? resolve(process.cwd(), options.workspace) + : resolvePath(resolved.workspace, resolved.root) } + + if (!builtinPools.includes(resolved.pool as BuiltinPool)) + resolved.pool = resolvePath(resolved.pool, resolved.root) resolved.poolMatchGlobs = (resolved.poolMatchGlobs || []).map(([glob, pool]) => { - if (!builtinPools.includes(pool as BuiltinPool)) { - pool = normalize( - resolveModule(pool, { paths: [resolved.root] }) - ?? resolve(resolved.root, pool), - ) - } + if (!builtinPools.includes(pool as BuiltinPool)) + pool = resolvePath(pool, resolved.root) return [glob, pool] }) @@ -315,16 +319,10 @@ export function resolveConfig( } resolved.setupFiles = toArray(resolved.setupFiles || []).map(file => - normalize( - resolveModule(file, { paths: [resolved.root] }) - ?? resolve(resolved.root, file), - ), + resolvePath(file, resolved.root), ) resolved.globalSetup = toArray(resolved.globalSetup || []).map(file => - normalize( - resolveModule(file, { paths: [resolved.root] }) - ?? resolve(resolved.root, file), - ), + resolvePath(file, resolved.root), ) resolved.coverage.exclude.push(...resolved.setupFiles.map(file => `${resolved.coverage.allowExternal ? '**/' : ''}${relative(resolved.root, file)}`)) @@ -334,10 +332,7 @@ export function resolveConfig( ] if (resolved.diff) { - resolved.diff = normalize( - resolveModule(resolved.diff, { paths: [resolved.root] }) - ?? resolve(resolved.root, resolved.diff), - ) + resolved.diff = resolvePath(resolved.diff, resolved.root) resolved.forceRerunTriggers.push(resolved.diff) } diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 480ca1600b68..09fa54bc6286 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -182,19 +182,31 @@ export class Vitest { || this.projects[0] } - private async resolveWorkspace(cliOptions: UserConfig) { + private async getWorkspaceConfigPath() { + if (this.config.workspace) + return this.config.workspace + const configDir = this.server.config.configFile ? dirname(this.server.config.configFile) : this.config.root + const rootFiles = await fs.readdir(configDir) + const workspaceConfigName = workspaceFiles.find((configFile) => { return rootFiles.includes(configFile) }) if (!workspaceConfigName) - return [await this.createCoreProject()] + return null - const workspaceConfigPath = join(configDir, workspaceConfigName) + return join(configDir, workspaceConfigName) + } + + private async resolveWorkspace(cliOptions: UserConfig) { + const workspaceConfigPath = await this.getWorkspaceConfigPath() + + if (!workspaceConfigPath) + return [await this.createCoreProject()] const workspaceModule = await this.runner.executeFile(workspaceConfigPath) as { default: (string | UserWorkspaceConfig)[] @@ -244,7 +256,7 @@ export class Vitest { const workspacesByFolder = resolvedWorkspacesPaths .reduce((configByFolder, filepath) => { - const dir = dirname(filepath) + const dir = filepath.endsWith('/') ? filepath.slice(0, -1) : dirname(filepath) configByFolder[dir] ??= [] configByFolder[dir].push(filepath) return configByFolder diff --git a/packages/vitest/src/node/plugins/workspace.ts b/packages/vitest/src/node/plugins/workspace.ts index ece33f763468..f6df0efd2c81 100644 --- a/packages/vitest/src/node/plugins/workspace.ts +++ b/packages/vitest/src/node/plugins/workspace.ts @@ -1,4 +1,4 @@ -import { dirname, relative } from 'pathe' +import { basename, dirname, relative } from 'pathe' import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite' import { configDefaults } from '../../defaults' import { generateScopedClassName } from '../../integrations/css/css-modules' @@ -36,7 +36,7 @@ export function WorkspaceVitestPlugin(project: WorkspaceProject, options: Worksp let name = testConfig.name if (!name) { if (typeof options.workspacePath === 'string') - name = dirname(options.workspacePath).split('/').pop() + name = basename(options.workspacePath.endsWith('/') ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath)) else name = options.workspacePath.toString() } diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 4357638787c3..cb449c11e703 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -33,7 +33,11 @@ export async function initializeProject(workspacePath: string | number, ctx: Vit ? false : workspacePath - const root = options.root || (typeof workspacePath === 'number' ? undefined : dirname(workspacePath)) + const root = options.root || ( + typeof workspacePath === 'number' + ? undefined + : workspacePath.endsWith('/') ? workspacePath : dirname(workspacePath) + ) const config: ViteInlineConfig = { ...options, diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index df434c519bb1..3064c1b419d4 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -309,6 +309,11 @@ export interface InlineConfig { */ poolMatchGlobs?: [string, Exclude][] + /** + * Path to a workspace configuration file + */ + workspace?: string + /** * Update snapshot * diff --git a/test/config/fixtures/workspace/nested/e2e.projects.js b/test/config/fixtures/workspace/nested/e2e.projects.js new file mode 100644 index 000000000000..6c95de1c895f --- /dev/null +++ b/test/config/fixtures/workspace/nested/e2e.projects.js @@ -0,0 +1 @@ +export default ['project-1'] \ No newline at end of file diff --git a/test/config/fixtures/workspace/project-1/calculator-1.test.ts b/test/config/fixtures/workspace/project-1/calculator-1.test.ts new file mode 100644 index 000000000000..e534944c53d5 --- /dev/null +++ b/test/config/fixtures/workspace/project-1/calculator-1.test.ts @@ -0,0 +1,5 @@ +import { expect, it } from 'vitest'; + +it('1 + 1 = 2', () => { + expect(1 + 1).toBe(2); +}) diff --git a/test/config/fixtures/workspace/project-2/calculator-2.test.ts b/test/config/fixtures/workspace/project-2/calculator-2.test.ts new file mode 100644 index 000000000000..62640ee642e6 --- /dev/null +++ b/test/config/fixtures/workspace/project-2/calculator-2.test.ts @@ -0,0 +1,5 @@ +import { expect, it } from 'vitest'; + +it('2 + 2 = 4', () => { + expect(2 + 2).toBe(4); +}) diff --git a/test/config/test/workspace.test.ts b/test/config/test/workspace.test.ts new file mode 100644 index 000000000000..a427bb53d701 --- /dev/null +++ b/test/config/test/workspace.test.ts @@ -0,0 +1,9 @@ +import { expect, it } from 'vitest' +import { runVitestCli } from '../../test-utils' + +it('correctly runs workspace tests when workspace config path is specified', async () => { + const { stderr, stdout } = await runVitestCli('run', '--root', 'fixtures/workspace', '--workspace', './nested/e2e.projects.js') + expect(stderr).toBe('') + expect(stdout).toContain('1 + 1 = 2') + expect(stdout).not.toContain('2 + 2 = 4') +}) diff --git a/test/workspaces/vitest.workspace.ts b/test/workspaces/vitest.workspace.ts index 08876bcbd56e..e32cad7ff02b 100644 --- a/test/workspaces/vitest.workspace.ts +++ b/test/workspaces/vitest.workspace.ts @@ -4,7 +4,7 @@ import remapping from '@ampproject/remapping' import type { Plugin } from 'vite' export default defineWorkspace([ - './space_2/*', + 'space_2', './space_*/*.config.ts', { test: {