diff --git a/docs/guide/index.md b/docs/guide/index.md index 8b979021f9a8..5f362e513ce1 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -116,6 +116,7 @@ You can specify additional CLI options like `--port` or `--https`. For a full li | `--dom` | Mock browser api with happy-dom | | `--environment ` | Runner environment (default: node) | | `--passWithNoTests` | Pass when no tests found | +| `--allowOnly` | Allow tests and suites that are marked as `only` (default: false in CI, true otherwise) | | `-h, --help` | Display available CLI options | ## Examples diff --git a/package.json b/package.json index 7876d4729b6e..ae57b964fdd4 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "release": "bumpp package.json packages/*/package.json --commit --push --tag && pnpm -r publish --access public", "test": "vitest --api -r test/core", "test:run": "vitest run -r test/core", - "test:all": "cross-env CI=true pnpm -r --stream --filter !@vitest/monorepo run test --", - "test:ci": "cross-env CI=true pnpm -r --stream --filter !@vitest/monorepo --filter !test-fails run test --", + "test:all": "cross-env CI=true pnpm -r --stream --filter !@vitest/monorepo run test -- --allowOnly", + "test:ci": "cross-env CI=true pnpm -r --stream --filter !@vitest/monorepo --filter !test-fails run test -- --allowOnly", "typecheck": "tsc --noEmit", "ui:build": "vite build packages/ui", "ui:dev": "vite packages/ui" diff --git a/packages/ui/client/components/StatusIcon.vue b/packages/ui/client/components/StatusIcon.vue index 9dcd6b82f3be..121c3d17d7d7 100644 --- a/packages/ui/client/components/StatusIcon.vue +++ b/packages/ui/client/components/StatusIcon.vue @@ -33,7 +33,7 @@ defineProps<{ task: Task }>() i-carbon:document-blank />
', 'runner environment', { default: 'node' }) .option('--passWithNoTests', 'pass when no tests found') + .option('--allowOnly', 'Allow tests and suites that are marked as only', { default: !process.env.CI }) .help() cli diff --git a/packages/vitest/src/runtime/collect.ts b/packages/vitest/src/runtime/collect.ts index 894ed7ad06fc..550d9fb9aa83 100644 --- a/packages/vitest/src/runtime/collect.ts +++ b/packages/vitest/src/runtime/collect.ts @@ -1,6 +1,6 @@ import { createHash } from 'crypto' import { relative } from 'pathe' -import type { File, ResolvedConfig, Suite } from '../types' +import type { File, ResolvedConfig, Suite, TaskBase } from '../types' import { clearContext, defaultSuite } from './suite' import { getHooks, setHooks } from './map' import { processError } from './error' @@ -64,7 +64,8 @@ export async function collectTests(paths: string[], config: ResolvedConfig) { calculateHash(file) - interpretTaskModes(file, config.testNamePattern) + const hasOnlyTasks = someTasksAreOnly(file) + interpretTaskModes(file, config.testNamePattern, hasOnlyTasks, false, config.allowOnly) files.push(file) } @@ -75,23 +76,25 @@ export async function collectTests(paths: string[], config: ResolvedConfig) { /** * If any tasks been marked as `only`, mark all other tasks as `skip`. */ -function interpretTaskModes(suite: Suite, namePattern?: string | RegExp, onlyMode?: boolean, isIncluded?: boolean) { - if (onlyMode === undefined) { - onlyMode = someTasksAreOnly(suite) - isIncluded ||= suite.mode === 'only' - } +function interpretTaskModes(suite: Suite, namePattern?: string | RegExp, onlyMode?: boolean, parentIsOnly?: boolean, allowOnly?: boolean) { + const suiteIsOnly = parentIsOnly || suite.mode === 'only' suite.tasks.forEach((t) => { // Check if either the parent suite or the task itself are marked as included - const includeTask = isIncluded || t.mode === 'only' + const includeTask = suiteIsOnly || t.mode === 'only' if (onlyMode) { if (t.type === 'suite' && (includeTask || someTasksAreOnly(t))) { // Don't skip this suite - if (t.mode === 'only') + if (t.mode === 'only') { + checkAllowOnly(t, allowOnly) t.mode = 'run' + } } else if (t.mode === 'run' && !includeTask) { t.mode = 'skip' } - else if (t.mode === 'only') { t.mode = 'run' } + else if (t.mode === 'only') { + checkAllowOnly(t, allowOnly) + t.mode = 'run' + } } if (t.type === 'test') { if (namePattern && !t.name.match(namePattern)) @@ -101,7 +104,7 @@ function interpretTaskModes(suite: Suite, namePattern?: string | RegExp, onlyMod if (t.mode === 'skip') skipAllTasks(t) else - interpretTaskModes(t, namePattern, onlyMode, includeTask) + interpretTaskModes(t, namePattern, onlyMode, includeTask, allowOnly) } }) @@ -126,6 +129,14 @@ function skipAllTasks(suite: Suite) { }) } +function checkAllowOnly(task: TaskBase, allowOnly?: boolean) { + if (allowOnly) return + task.result = { + state: 'fail', + error: processError(new Error('[Vitest] Unexpected .only modifier. Remove it or pass --allowOnly arguement to bypass this error')), + } +} + function calculateHash(parent: Suite) { parent.tasks.forEach((t, idx) => { t.id = `${parent.id}_${idx}` diff --git a/packages/vitest/src/runtime/run.ts b/packages/vitest/src/runtime/run.ts index c4df6b379e2e..d92145981a14 100644 --- a/packages/vitest/src/runtime/run.ts +++ b/packages/vitest/src/runtime/run.ts @@ -47,6 +47,11 @@ export async function runTest(test: Test) { if (test.mode !== 'run') return + if (test.result?.state === 'fail') { + updateTask(test) + return + } + const start = performance.now() test.result = { @@ -114,9 +119,21 @@ export async function runTest(test: Test) { updateTask(test) } +function markTasksAsSkipped(suite: Suite) { + suite.tasks.forEach((t) => { + t.mode = 'skip' + t.result = { ...t.result, state: 'skip' } + updateTask(t) + if (t.type === 'suite') markTasksAsSkipped(t) + }) +} + export async function runSuite(suite: Suite) { - if (suite.result?.state === 'fail') + if (suite.result?.state === 'fail') { + markTasksAsSkipped(suite) + updateTask(suite) return + } const start = performance.now() diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index cbe79f253c1c..43b67f57b84f 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -305,6 +305,11 @@ export interface UserConfig extends InlineConfig { */ passWithNoTests?: boolean + /** + * Allow tests and suites that are marked as only + */ + allowOnly?: boolean + /** * Run tests that cover a list of source files */