diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9ce85c45488..741aa592c48f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,35 @@ jobs: - name: Test UI run: pnpm run ui:test + test-ui-e2e: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + fail-fast: false + + runs-on: ${{ matrix.os }} + + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-and-cache + with: + node-version: 20 + + - name: Install + run: pnpm i + + - name: Install Playwright Dependencies + run: pnpx playwright install chromium + + - name: Build + run: pnpm run build + + - name: Test + run: pnpm -C test/ui test-e2e + test-browser: runs-on: ubuntu-latest strategy: diff --git a/.vscode/settings.json b/.vscode/settings.json index 0d399d6d837b..22dac83216fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,8 +13,8 @@ // Auto fix "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.organizeImports": false + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" }, // Silent the stylistic rules in you IDE, but still auto fix them diff --git a/docs/advanced/pool.md b/docs/advanced/pool.md index b8c9e199da0d..a8bc3e7de444 100644 --- a/docs/advanced/pool.md +++ b/docs/advanced/pool.md @@ -53,7 +53,7 @@ Vitest calls `runTest` when new tests are scheduled to run. It will not call it Vitest will wait until `runTests` is executed before finishing a run (i.e., it will emit [`onFinished`](/guide/reporters) only after `runTests` is resolved). -If you are using a custom pool, you will have to provide test files and their results yourself - you can reference [`vitest.state`](https://github.com/vitest-dev/vitest/blob/feat/custom-pool/packages/vitest/src/node/state.ts) for that (most important are `collectFiles` and `updateTasks`). Vitest uses `startTests` function from `@vitest/runner` package to do that. +If you are using a custom pool, you will have to provide test files and their results yourself - you can reference [`vitest.state`](https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/state.ts) for that (most important are `collectFiles` and `updateTasks`). Vitest uses `startTests` function from `@vitest/runner` package to do that. To communicate between different processes, you can create methods object using `createMethodsRPC` from `vitest/node`, and use any form of communication that you prefer. For example, to use WebSockets with `birpc` you can write something like this: @@ -87,4 +87,4 @@ async function runTests(project: WorkspaceProject, tests: string[]) { } ``` -You can see a simple example in [pool/custom-pool.ts](https://github.com/vitest-dev/vitest/blob/feat/custom-pool/test/run/pool-custom-fixtures/pool/custom-pool.ts). +You can see a simple example in [pool/custom-pool.ts](https://github.com/vitest-dev/vitest/blob/main/test/run/pool-custom-fixtures/pool/custom-pool.ts). diff --git a/docs/config/index.md b/docs/config/index.md index 597bc3e4003c..7150b84bb24a 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -879,6 +879,33 @@ Pass additional arguments to `node` process in the VM context. See [Command-line Be careful when using, it as some options may crash worker, e.g. --prof, --title. See https://github.com/nodejs/node/issues/41103. ::: +### fileParallelism + +- **Type:** `boolean` +- **Default:** `true` +- **CLI:** `--no-file-parallelism`, `--fileParallelism=false` +- **Version:** Since Vitest 1.1 + +Should all test files run in parallel. Setting this to `false` will override `maxWorkers` and `minWorkers` options to `1`. + +::: tip +This option doesn't affect tests running in the same file. If you want to run those in parallel, use `concurrent` option on [describe](/api/#describe-concurrent) or via [a config](#sequence-concurrent). +::: + +### maxWorkers + +- **Type:** `number` +- **Version:** Since Vitest 1.1 + +Maximum number of workers to run tests in. `poolOptions.{threads,vmThreads}.maxThreads`/`poolOptions.forks.maxForks` has higher priority. + +### minWorkers + +- **Type:** `number` +- **Version:** Since Vitest 1.1 + +Minimum number of workers to run tests in. `poolOptions.{threads,vmThreads}.minThreads`/`poolOptions.forks.minForks` has higher priority. + ### testTimeout - **Type:** `number` @@ -1126,6 +1153,8 @@ Clean coverage report on watch rerun Directory to write coverage report to. +To preview the coverage report in the output of [HTML reporter](/guide/reporters.html#html-reporter), this option must be set as a sub-directory of the html report directory (for example `./html/coverage`). + #### coverage.reporter - **Type:** `string | string[] | [string, {}][]` @@ -2028,3 +2057,27 @@ 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). + +### isolate + +- **Type:** `boolean` +- **Default:** `true` +- **CLI:** `--no-isolate`, `--isolate=false` +- **Version:** Since Vitest 1.1.0 + +Run tests in an isolated environment. This option has no effect on `vmThreads` pool. + +Disabling this option might improve [performance](/guide/performance) if your code doesn't rely on side effects (which is usually true for projects with `node` environment). + +::: note +You can disable isolation for specific pools by using [`poolOptions`](#pooloptions) property. +::: diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 66a0661c4d3a..fc8140e9baa9 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -73,13 +73,17 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim | `--poolOptions ` | Specify pool options | | `--poolOptions.threads.isolate` | Isolate tests in threads pool (default: `true`) | | `--poolOptions.forks.isolate` | Isolate tests in forks pool (default: `true`) | +| `--fileParallelism` | Should all test files run in parallel. Use --no-parallelism to disable (default: true) | +| `--maxWorkers` | Maximum number of workers to run tests in | +| `--minWorkers` | Minimum number of workers to run tests in | | `--silent` | Silent console output from tests | | `--reporter ` | Select reporter: `default`, `verbose`, `dot`, `junit`, `json`, or a path to a custom reporter | | `--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`) | +| `--isolate` | Run every test file in isolation. To disable isolation, use --no-isolate (default: `true`) | | `--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`) | @@ -96,6 +100,7 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim | `--inspect-brk` | Enables Node.js inspector with break | | `--bail ` | Stop test execution when given number of tests have failed | | `--retry ` | Retry the test specific number of times if it fails | +| `--exclude ` | Additional file globs to be excluded from test | | `--expand-snapshot-diff` | Show full diff when snapshot fails | | `--typecheck [options]` | Custom options for typecheck pool. If passed without options, enables typechecking | | `--typecheck.enabled` | Enable typechecking alongside tests (default: `false`) | diff --git a/docs/guide/debugging.md b/docs/guide/debugging.md index a4dcdf4169e9..97ecaebe3341 100644 --- a/docs/guide/debugging.md +++ b/docs/guide/debugging.md @@ -62,6 +62,13 @@ vitest --inspect-brk --pool threads --poolOptions.threads.singleThread vitest --inspect-brk --pool forks --poolOptions.forks.singleFork ``` +If you are using Vitest 1.1 or higher, you can also just provide `--no-parallelism` flag: + +```sh +# If pool is unknown +vitest --inspect-brk --no-file-parallelism +``` + Once Vitest starts it will stop execution and wait for you to open developer tools that can connect to [Node.js inspector](https://nodejs.org/en/docs/guides/debugging-getting-started/). You can use Chrome DevTools for this by opening `chrome://inspect` on browser. In watch mode you can keep the debugger open during test re-runs by using the `--poolOptions.threads.isolate false` options. diff --git a/docs/guide/performance.md b/docs/guide/performance.md new file mode 100644 index 000000000000..2462221053c6 --- /dev/null +++ b/docs/guide/performance.md @@ -0,0 +1,51 @@ +# Performance + +By default Vitest runs every test file in an isolated environment based on the [pool](/config/#pool): + +- `threads` pool runs every test file in a separate [`Worker`](https://nodejs.org/api/worker_threads.html#class-worker) +- `forks` pool runs every test file in a separate [forked child process](https://nodejs.org/api/child_process.html#child_processforkmodulepath-args-options) +- `vmThreads` pool runs every test file in a separate [VM context](https://nodejs.org/api/vm.html#vmcreatecontextcontextobject-options), but it uses workers for parallelism + +This greatly increases test times, which might not be desirable for projects that don't rely on side effects and properly cleanup their state (which is usually true for projects with `node` environment). In this case disabling isolation will improve the speed of your tests. To do that, you can provide `--no-isolate` flag to the CLI or set [`test.isolate`](/config/#isolate) property in the config to `false`. If you are using several pools at once with `poolMatchGlobs`, you can also disable isolation for a specific pool you are using. + +::: code-group +```bash [CLI] +vitest --no-isolate +``` +```ts [vitest.config.js] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + isolate: false, + // you can also disable isolation only for specific pools + poolOptions: { + forks: { + isolate: false, + }, + }, + }, +}) +``` +::: + +::: note +If you are using `vmThreads` pool, you cannot disable isolation. Use `threads` pool instead to improve your tests performance. +::: + +For some projects, it might also be desirable to disable parallelism to improve startup time. To do that, provide `--no-file-parallelism` flag to the CLI or set [`test.fileParallelism`](/config/#fileParallelism) property in the config to `false`. + +::: code-group +```bash [CLI] +vitest --no-file-parallelism +``` +```ts [vitest.config.js] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + fileParallelism: false, + }, +}) +``` +::: \ No newline at end of file diff --git a/docs/package.json b/docs/package.json index 4f89cb488139..afb09ddbd271 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,8 +17,8 @@ "vue": "latest" }, "devDependencies": { - "@iconify-json/carbon": "^1.1.24", - "@iconify-json/logos": "^1.1.40", + "@iconify-json/carbon": "^1.1.27", + "@iconify-json/logos": "^1.1.41", "@unocss/reset": "^0.57.4", "@vite-pwa/assets-generator": "^0.0.11", "@vite-pwa/vitepress": "^0.2.3", diff --git a/package.json b/package.json index 64864ae62826..e6ee2198a8bd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/monorepo", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "private": true, "packageManager": "pnpm@8.10.3", "description": "Next generation testing framework powered by Vite", @@ -49,7 +49,7 @@ "@vitest/coverage-istanbul": "workspace:*", "@vitest/coverage-v8": "workspace:*", "@vitest/ui": "workspace:*", - "bumpp": "^9.2.0", + "bumpp": "^9.2.1", "esbuild": "^0.19.5", "eslint": "^8.54.0", "fast-glob": "^3.3.2", diff --git a/packages/browser/package.json b/packages/browser/package.json index ddf9dd40f46e..91864b90a083 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/browser", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Browser running for Vitest", "license": "MIT", "funding": "https://opencollective.com/vitest", diff --git a/packages/coverage-istanbul/package.json b/packages/coverage-istanbul/package.json index 34aa069bdc23..5175f658ba73 100644 --- a/packages/coverage-istanbul/package.json +++ b/packages/coverage-istanbul/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/coverage-istanbul", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Istanbul coverage provider for Vitest", "author": "Anthony Fu ", "license": "MIT", diff --git a/packages/coverage-v8/package.json b/packages/coverage-v8/package.json index 55a390cb0967..560a2862967b 100644 --- a/packages/coverage-v8/package.json +++ b/packages/coverage-v8/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/coverage-v8", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "V8 coverage provider for Vitest", "author": "Anthony Fu ", "license": "MIT", diff --git a/packages/expect/package.json b/packages/expect/package.json index 17376ac57ca5..2fd83444ba96 100644 --- a/packages/expect/package.json +++ b/packages/expect/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/expect", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Jest's expect matchers as a Chai plugin", "license": "MIT", "funding": "https://opencollective.com/vitest", diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index 0c8fa719863f..0434c3705233 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -330,7 +330,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { if (Array.isArray(args[0])) args[0] = args[0].map(key => String(key).replace(/([.[\]])/g, '\\$1')).join('.') - const actual = this._obj + const actual = this._obj as any const [propertyName, expected] = args const getValue = () => { const hasOwn = Object.prototype.hasOwnProperty.call(actual, propertyName) @@ -347,7 +347,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { pass, `expected #{this} to have property "${propertyName}"${valueString}`, `expected #{this} to not have property "${propertyName}"${valueString}`, - actual, + expected, + exists ? value : undefined, ) }) def('toBeCloseTo', function (received: number, precision = 2) { diff --git a/packages/runner/package.json b/packages/runner/package.json index aaf195d0cb9c..a8028fa67fac 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/runner", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Vitest test runner", "license": "MIT", "funding": "https://opencollective.com/vitest", diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index ec842072f148..9faeb012e6aa 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -105,9 +105,11 @@ async function resolveFixtureFunction( ): Promise { // wait for `use` call to extract fixture value const useFnArgPromise = createDefer() + let isUseFnArgResolved = false const fixtureReturn = fixtureFn(context, async (useFnArg: unknown) => { // extract `use` argument + isUseFnArgResolved = true useFnArgPromise.resolve(useFnArg) // suspend fixture teardown by holding off `useReturnPromise` resolution until cleanup @@ -119,7 +121,15 @@ async function resolveFixtureFunction( await fixtureReturn }) await useReturnPromise - }).catch(useFnArgPromise.reject) // treat fixture function error (until `use` call) as test failure + }).catch((e: unknown) => { + // treat fixture setup error as test failure + if (!isUseFnArgResolved) { + useFnArgPromise.reject(e) + return + } + // otherwise re-throw to avoid silencing error during cleanup + throw e + }) return useFnArgPromise } diff --git a/packages/snapshot/package.json b/packages/snapshot/package.json index 7b0848b36c6e..e62b600ae054 100644 --- a/packages/snapshot/package.json +++ b/packages/snapshot/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/snapshot", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Vitest snapshot manager", "license": "MIT", "funding": "https://opencollective.com/vitest", diff --git a/packages/spy/package.json b/packages/spy/package.json index 2f68c9dc3abf..bde6b24de1d5 100644 --- a/packages/spy/package.json +++ b/packages/spy/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/spy", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Lightweight Jest compatible spy implementation", "license": "MIT", "funding": "https://opencollective.com/vitest", diff --git a/packages/ui/client/components/TaskTree.vue b/packages/ui/client/components/TaskTree.vue index ebef882b5efa..a8608c200161 100644 --- a/packages/ui/client/components/TaskTree.vue +++ b/packages/ui/client/components/TaskTree.vue @@ -17,7 +17,7 @@ const { task, indent = 0, nested = false, search, onItemClick } = defineProps<{ { const data = logs.value if (data) { const filter = createAnsiToHtmlFilter(isDark.value) - return data.map(({ taskId, type, time, content }) => { - const trimmed = content.trim() - const value = filter.toHtml(trimmed) - return value !== trimmed - ? { taskId, type, time, html: true, content: value } - : { taskId, type, time, html: false, content } - }) + return data.map(({ taskId, type, time, content }) => ({ taskId, type, time, content: filter.toHtml(escapeHtml(content)) })) } }) @@ -26,13 +21,12 @@ function getTaskName(id?: string) { diff --git a/packages/ui/client/components/views/ViewReport.vue b/packages/ui/client/components/views/ViewReport.vue index 9e6c13754d2b..4e0e8fff9882 100644 --- a/packages/ui/client/components/views/ViewReport.vue +++ b/packages/ui/client/components/views/ViewReport.vue @@ -5,6 +5,7 @@ import ViewReportError from './ViewReportError.vue' import { isDark } from '~/composables/dark' import { createAnsiToHtmlFilter } from '~/composables/error' import { config } from '~/composables/client' +import { escapeHtml } from '~/utils/escape' const props = defineProps<{ file?: File @@ -24,15 +25,6 @@ function collectFailed(task: Task, level: number): LeveledTask[] { return [{ ...task, level }, ...task.tasks.flatMap(t => collectFailed(t, level + 1))] } -function escapeHtml(unsafe: string) { - return unsafe - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') -} - function createHtmlError(filter: Convert, error: ErrorWithDiff) { let htmlError = '' if (error.message?.includes('\x1B')) diff --git a/packages/ui/client/composables/navigation.ts b/packages/ui/client/composables/navigation.ts index d4229407efb9..1054c1790abc 100644 --- a/packages/ui/client/composables/navigation.ts +++ b/packages/ui/client/composables/navigation.ts @@ -7,19 +7,16 @@ export const dashboardVisible = ref(true) export const coverageVisible = ref(false) export const disableCoverage = ref(true) export const coverage = computed(() => config.value?.coverage) -export const coverageConfigured = computed(() => { - if (!config.value?.api?.port) - return false - - return coverage.value?.enabled -}) +export const coverageConfigured = computed(() => coverage.value?.enabled) export const coverageEnabled = computed(() => { return coverageConfigured.value && coverage.value.reporter.map(([reporterName]) => reporterName).includes('html') }) +// TODO +// For html report preview, "coverage.reportsDirectory" must be explicitly set as a subdirectory of html report. +// Handling other cases seems difficult, so this limitation is mentioned in the documentation for now. export const coverageUrl = computed(() => { if (coverageEnabled.value) { - const url = `${window.location.protocol}//${window.location.hostname}:${config.value!.api!.port!}` const idx = coverage.value!.reportsDirectory.lastIndexOf('/') const htmlReporter = coverage.value!.reporter.find((reporter) => { if (reporter[0] !== 'html') @@ -28,8 +25,8 @@ export const coverageUrl = computed(() => { return reporter }) return htmlReporter && 'subdir' in htmlReporter[1] - ? `${url}/${coverage.value!.reportsDirectory.slice(idx + 1)}/${htmlReporter[1].subdir}/index.html` - : `${url}/${coverage.value!.reportsDirectory.slice(idx + 1)}/index.html` + ? `/${coverage.value!.reportsDirectory.slice(idx + 1)}/${htmlReporter[1].subdir}/index.html` + : `/${coverage.value!.reportsDirectory.slice(idx + 1)}/index.html` } return undefined diff --git a/packages/ui/client/utils/escape.ts b/packages/ui/client/utils/escape.ts new file mode 100644 index 000000000000..27676fac160e --- /dev/null +++ b/packages/ui/client/utils/escape.ts @@ -0,0 +1,8 @@ +export function escapeHtml(unsafe: string) { + return unsafe + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} diff --git a/packages/ui/package.json b/packages/ui/package.json index e98769f74cf9..b1fe91056bff 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/ui", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "UI for Vitest", "license": "MIT", "funding": "https://opencollective.com/vitest", @@ -59,7 +59,7 @@ }, "devDependencies": { "@faker-js/faker": "^8.2.0", - "@iconify-json/logos": "^1.1.40", + "@iconify-json/logos": "^1.1.41", "@testing-library/cypress": "^10.0.1", "@types/codemirror": "^5.60.13", "@types/d3-force": "^3.0.9", diff --git a/packages/utils/package.json b/packages/utils/package.json index b9a39022fafc..e98a1bbd185f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/utils", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Shared Vitest utility functions", "license": "MIT", "funding": "https://opencollective.com/vitest", diff --git a/packages/vite-node/package.json b/packages/vite-node/package.json index 0c80d2c345c1..14fe474a5b9d 100644 --- a/packages/vite-node/package.json +++ b/packages/vite-node/package.json @@ -1,7 +1,7 @@ { "name": "vite-node", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Vite as Node.js runtime", "author": "Anthony Fu ", "license": "MIT", diff --git a/packages/vite-node/src/cli.ts b/packages/vite-node/src/cli.ts index 39794c21dee9..37c86d368ab9 100644 --- a/packages/vite-node/src/cli.ts +++ b/packages/vite-node/src/cli.ts @@ -1,3 +1,4 @@ +import { resolve } from 'node:path' import cac from 'cac' import c from 'picocolors' import { createServer } from 'vite' @@ -54,7 +55,7 @@ async function run(files: string[], options: CliOptions = {}) { if (options.script) { files = [files[0]] options = {} - process.argv = [process.argv[0], files[0], ...process.argv.slice(2).filter(arg => arg !== '--script' && arg !== files[0])] + process.argv = [process.argv[0], resolve(files[0]), ...process.argv.slice(2).filter(arg => arg !== '--script' && arg !== files[0])] } else { process.argv = [...process.argv.slice(0, 2), ...(options['--'] || [])] diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 3f2f7e4387d6..9973375d7c8c 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -1,7 +1,7 @@ { "name": "vitest", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Next generation testing framework powered by Vite", "author": "Anthony Fu ", "license": "MIT", @@ -169,7 +169,7 @@ "@types/istanbul-reports": "^3.0.4", "@types/jsdom": "^21.1.6", "@types/micromatch": "^4.0.6", - "@types/prompts": "^2.4.8", + "@types/prompts": "^2.4.9", "@types/sinonjs__fake-timers": "^8.1.5", "birpc": "0.2.14", "chai-subset": "^1.6.0", diff --git a/packages/vitest/src/config.ts b/packages/vitest/src/config.ts index e08dcab0fa05..b3e71144ff7a 100644 --- a/packages/vitest/src/config.ts +++ b/packages/vitest/src/config.ts @@ -30,6 +30,8 @@ export function defineProject(config: T): T { return config } -export function defineWorkspace(config: T): T { +type Workspace = (string | (UserProjectConfigExport & { extends?: string })) + +export function defineWorkspace(config: Workspace[]): Workspace[] { return config } diff --git a/packages/vitest/src/defaults.ts b/packages/vitest/src/defaults.ts index 2d1b2ad57e8a..35615a5636f2 100644 --- a/packages/vitest/src/defaults.ts +++ b/packages/vitest/src/defaults.ts @@ -62,6 +62,7 @@ export const fakeTimersDefaults = { const config = { allowOnly: !isCI, + isolate: true, watch: !isCI, globals: false, environment: 'node' as const, @@ -100,6 +101,6 @@ const config = { exclude: defaultExclude, }, slowTestThreshold: 300, -} +} satisfies UserConfig -export const configDefaults: Required> = Object.freeze(config) +export const configDefaults = Object.freeze(config) diff --git a/packages/vitest/src/node/cli.ts b/packages/vitest/src/node/cli.ts index 598fc07f53ed..77734459a400 100644 --- a/packages/vitest/src/node/cli.ts +++ b/packages/vitest/src/node/cli.ts @@ -29,6 +29,8 @@ 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('--isolate', 'Run every test file in isolation. To disable isolation, use --no-isolate (default: true)') .option('--globals', 'Inject apis globally') .option('--dom', 'Mock browser API with happy-dom') .option('--browser [options]', 'Run tests in the browser (default: false)') @@ -36,6 +38,9 @@ cli .option('--poolOptions ', 'Specify pool options') .option('--poolOptions.threads.isolate', 'Isolate tests in threads pool (default: true)') .option('--poolOptions.forks.isolate', 'Isolate tests in forks pool (default: true)') + .option('--fileParallelism', 'Should all test files run in parallel. Use --no-file-parallelism to disable (default: true)') + .option('--maxWorkers', 'Maximum number of workers to run tests in') + .option('--minWorkers', 'Minimum number of workers to run tests in') .option('--environment ', 'Specify runner environment, if not running in the browser (default: node)') .option('--passWithNoTests', 'Pass when no tests found') .option('--logHeapUsage', 'Show the size of heap for each test') @@ -52,6 +57,7 @@ cli .option('--bail ', 'Stop test execution when given number of tests have failed (default: 0)') .option('--retry ', 'Retry the test specific number of times if it fails (default: 0)') .option('--diff ', 'Path to a diff config that will be used to generate diff interface') + .option('--exclude ', 'Additional file globs to be excluded from test') .option('--expand-snapshot-diff', 'Show full diff when snapshot fails') .option('--typecheck [options]', 'Custom options for typecheck pool') .option('--typecheck.enabled', 'Enable typechecking alongside tests (default: false)') @@ -150,11 +156,21 @@ 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 delete argv.dir + if (argv.exclude) { + argv.cliExclude = toArray(argv.exclude) + delete argv.exclude + } + if (argv.coverage) { const coverage = argv.coverage if (coverage.exclude) diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 674b80dc1d0e..55ea0813a500 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 { @@ -113,13 +120,21 @@ export function resolveConfig( resolved.shard = { index, count } } + resolved.fileParallelism ??= true + + if (!resolved.fileParallelism) { + // ignore user config, parallelism cannot be implemented without limiting workers + resolved.maxWorkers = 1 + resolved.minWorkers = 1 + } + if (resolved.inspect || resolved.inspectBrk) { const isSingleThread = resolved.pool === 'threads' && resolved.poolOptions?.threads?.singleThread const isSingleFork = resolved.pool === 'forks' && resolved.poolOptions?.forks?.singleFork - if (!isSingleThread && !isSingleFork) { + if (resolved.fileParallelism && !isSingleThread && !isSingleFork) { const inspectOption = `--inspect${resolved.inspectBrk ? '-brk' : ''}` - throw new Error(`You cannot use ${inspectOption} without "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"`) + throw new Error(`You cannot use ${inspectOption} without "--no-parallelism", "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"`) } } @@ -177,6 +192,9 @@ export function resolveConfig( resolved.server.deps![option] = resolved.deps[option] as any }) + if (resolved.cliExclude) + resolved.exclude.push(...resolved.cliExclude) + // vitenode will try to import such file with native node, // but then our mocker will not work properly if (resolved.server.deps.inline !== true) { @@ -193,10 +211,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 +290,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 +330,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 +343,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 7476fedf667b..fcde2720a58d 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 @@ -264,8 +276,12 @@ export class Vitest { 'testTimeout', 'pool', 'globals', - 'mode', 'expandSnapshotDiff', + 'retry', + 'testNamePattern', + 'passWithNoTests', + 'bail', + 'isolate', ] as const const cliOverrides = overridesOptions.reduce((acc, name) => { diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index 6ffeabc06572..df0d95783ced 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -51,6 +51,13 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t ) testConfig.api = resolveApiServerConfig(testConfig) + testConfig.poolOptions ??= {} + testConfig.poolOptions.threads ??= {} + testConfig.poolOptions.forks ??= {} + // prefer --poolOptions.{name}.isolate CLI arguments over --isolate, but still respect config value + testConfig.poolOptions.threads.isolate = options.poolOptions?.threads?.isolate ?? options.isolate ?? testConfig.poolOptions.threads.isolate ?? viteConfig.test?.isolate + testConfig.poolOptions.forks.isolate = options.poolOptions?.forks?.isolate ?? options.isolate ?? testConfig.poolOptions.forks.isolate ?? viteConfig.test?.isolate + // store defines for globalThis to make them // reassignable when running in worker in src/runtime/setup.ts const defines: Record = deleteDefineConfig(viteConfig) @@ -91,6 +98,9 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t allow: resolveFsAllow(getRoot(), testConfig.config), }, }, + test: { + poolOptions: testConfig.poolOptions, + }, } // chokidar fsevents is unstable on macos when emitting "ready" event 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/pools/child.ts b/packages/vitest/src/node/pools/child.ts index 4891c0447550..513d3ad7a309 100644 --- a/packages/vitest/src/node/pools/child.ts +++ b/packages/vitest/src/node/pools/child.ts @@ -58,8 +58,10 @@ export function createChildProcessPool(ctx: Vitest, { execArgv, env, forksPath } ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1) - const maxThreads = ctx.config.poolOptions?.forks?.maxForks ?? threadsCount - const minThreads = ctx.config.poolOptions?.forks?.minForks ?? threadsCount + const poolOptions = ctx.config.poolOptions?.forks ?? {} + + const maxThreads = poolOptions.maxForks ?? ctx.config.maxWorkers ?? threadsCount + const minThreads = poolOptions.minForks ?? ctx.config.minWorkers ?? threadsCount const options: TinypoolOptions = { runtime: 'child_process', @@ -70,20 +72,20 @@ export function createChildProcessPool(ctx: Vitest, { execArgv, env, forksPath } env, execArgv: [ - ...ctx.config.poolOptions?.forks?.execArgv ?? [], + ...poolOptions.execArgv ?? [], ...execArgv, ], terminateTimeout: ctx.config.teardownTimeout, + concurrentTasksPerWorker: 1, } - if (ctx.config.poolOptions?.forks?.isolate ?? true) { + const isolated = poolOptions.isolate ?? true + + if (isolated) options.isolateWorkers = true - options.concurrentTasksPerWorker = 1 - } - if (ctx.config.poolOptions?.forks?.singleFork) { - options.concurrentTasksPerWorker = 1 + if (poolOptions.singleFork || !ctx.config.fileParallelism) { options.maxThreads = 1 options.minThreads = 1 } @@ -164,7 +166,7 @@ export function createChildProcessPool(ctx: Vitest, { execArgv, env, forksPath } const files = Object.values(filesByEnv).flat() const results: PromiseSettledResult[] = [] - if (ctx.config.poolOptions?.forks?.isolate ?? true) { + if (isolated) { results.push(...await Promise.allSettled(files.map(({ file, environment, project }) => runFiles(project, getConfig(project), [file], environment, invalidates)))) } diff --git a/packages/vitest/src/node/pools/threads.ts b/packages/vitest/src/node/pools/threads.ts index 4d78c458a546..c426cc6074c1 100644 --- a/packages/vitest/src/node/pools/threads.ts +++ b/packages/vitest/src/node/pools/threads.ts @@ -43,34 +43,36 @@ export function createThreadsPool(ctx: Vitest, { execArgv, env, workerPath }: Po ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1) - const maxThreads = ctx.config.poolOptions?.threads?.maxThreads ?? threadsCount - const minThreads = ctx.config.poolOptions?.threads?.minThreads ?? threadsCount + const poolOptions = ctx.config.poolOptions?.threads ?? {} + + const maxThreads = poolOptions.maxThreads ?? ctx.config.maxWorkers ?? threadsCount + const minThreads = poolOptions.minThreads ?? ctx.config.minWorkers ?? threadsCount const options: TinypoolOptions = { filename: workerPath, // TODO: investigate further // It seems atomics introduced V8 Fatal Error https://github.com/vitest-dev/vitest/issues/1191 - useAtomics: ctx.config.poolOptions?.threads?.useAtomics ?? false, + useAtomics: poolOptions.useAtomics ?? false, maxThreads, minThreads, env, execArgv: [ - ...ctx.config.poolOptions?.threads?.execArgv ?? [], + ...poolOptions.execArgv ?? [], ...execArgv, ], terminateTimeout: ctx.config.teardownTimeout, + concurrentTasksPerWorker: 1, } - if (ctx.config.poolOptions?.threads?.isolate ?? true) { + const isolated = poolOptions.isolate ?? true + + if (isolated) options.isolateWorkers = true - options.concurrentTasksPerWorker = 1 - } - if (ctx.config.poolOptions?.threads?.singleThread) { - options.concurrentTasksPerWorker = 1 + if (poolOptions.singleThread || !ctx.config.fileParallelism) { options.maxThreads = 1 options.minThreads = 1 } @@ -144,7 +146,7 @@ export function createThreadsPool(ctx: Vitest, { execArgv, env, workerPath }: Po const files = Object.values(filesByEnv).flat() const results: PromiseSettledResult[] = [] - if (ctx.config.poolOptions?.threads?.isolate ?? true) { + if (isolated) { results.push(...await Promise.allSettled(files.map(({ file, environment, project }) => runFiles(project, getConfig(project), [file], environment, invalidates)))) } diff --git a/packages/vitest/src/node/pools/typecheck.ts b/packages/vitest/src/node/pools/typecheck.ts index 281f2a71d5f6..19f811c1f596 100644 --- a/packages/vitest/src/node/pools/typecheck.ts +++ b/packages/vitest/src/node/pools/typecheck.ts @@ -96,7 +96,7 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool { setTimeout(() => { resolve(false) clearInterval(_i) - }, 500) + }, 500).unref() }) const triggered = await _p if (project.typechecker && !triggered) { diff --git a/packages/vitest/src/node/pools/vm-threads.ts b/packages/vitest/src/node/pools/vm-threads.ts index 143d506bb425..9b1c4f2f22c9 100644 --- a/packages/vitest/src/node/pools/vm-threads.ts +++ b/packages/vitest/src/node/pools/vm-threads.ts @@ -48,14 +48,16 @@ export function createVmThreadsPool(ctx: Vitest, { execArgv, env, vmPath }: Pool ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1) - const maxThreads = ctx.config.poolOptions?.vmThreads?.maxThreads ?? threadsCount - const minThreads = ctx.config.poolOptions?.vmThreads?.minThreads ?? threadsCount + const poolOptions = ctx.config.poolOptions?.vmThreads ?? {} + + const maxThreads = poolOptions.maxThreads ?? ctx.config.maxWorkers ?? threadsCount + const minThreads = poolOptions.minThreads ?? ctx.config.minWorkers ?? threadsCount const options: TinypoolOptions = { filename: vmPath, // TODO: investigate further // It seems atomics introduced V8 Fatal Error https://github.com/vitest-dev/vitest/issues/1191 - useAtomics: ctx.config.poolOptions?.vmThreads?.useAtomics ?? false, + useAtomics: poolOptions.useAtomics ?? false, maxThreads, minThreads, @@ -66,16 +68,16 @@ export function createVmThreadsPool(ctx: Vitest, { execArgv, env, vmPath }: Pool '--experimental-vm-modules', '--require', suppressWarningsPath, - ...ctx.config.poolOptions?.vmThreads?.execArgv ?? [], + ...poolOptions.execArgv ?? [], ...execArgv, ], terminateTimeout: ctx.config.teardownTimeout, + concurrentTasksPerWorker: 1, maxMemoryLimitBeforeRecycle: getMemoryLimit(ctx.config) || undefined, } - if (ctx.config.poolOptions?.vmThreads?.singleThread) { - options.concurrentTasksPerWorker = 1 + if (poolOptions.singleThread || !ctx.config.fileParallelism) { options.maxThreads = 1 options.minThreads = 1 } diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index d816ac87cb9b..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, @@ -41,7 +45,7 @@ export async function initializeProject(workspacePath: string | number, ctx: Vit logLevel: 'error', configFile, // this will make "mode": "test" | "benchmark" inside defineConfig - mode: options.mode || ctx.config.mode, + mode: options.test?.mode || options.mode || ctx.config.mode, plugins: [ ...options.plugins || [], WorkspaceVitestPlugin(project, { ...options, root, workspacePath }), @@ -360,8 +364,7 @@ export class WorkspaceProject { this.server.close(), this.typechecker?.stop(), this.browser?.close(), - () => this._provided = ({} as any), - ].filter(Boolean)) + ].filter(Boolean)).then(() => this._provided = {} as any) } return this.closingPromise } diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index df434c519bb1..6e91559d18d4 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -280,6 +280,15 @@ export interface InlineConfig { */ environmentMatchGlobs?: [string, VitestEnvironment][] + /** + * Run tests in an isolated environment. This option has no effect on vmThreads pool. + * + * Disabling this option might improve performance if your code doesn't rely on side effects. + * + * @default true + */ + isolate?: boolean + /** * Pool used to run tests in. * @@ -294,6 +303,23 @@ export interface InlineConfig { */ poolOptions?: PoolOptions + /** + * Maximum number of workers to run tests in. `poolOptions.{threads,vmThreads}.maxThreads`/`poolOptions.forks.maxForks` has higher priority. + */ + maxWorkers?: number + /** + * Minimum number of workers to run tests in. `poolOptions.{threads,vmThreads}.minThreads`/`poolOptions.forks.minForks` has higher priority. + */ + minWorkers?: number + + /** + * Should all test files run in parallel. Doesn't affect tests running in the same file. + * Setting this to `false` will override `maxWorkers` and `minWorkers` options to `1`. + * + * @default true + */ + fileParallelism?: boolean + /** * Automatically assign pool based on globs. The first match will be used. * @@ -309,6 +335,11 @@ export interface InlineConfig { */ poolMatchGlobs?: [string, Exclude][] + /** + * Path to a workspace configuration file + */ + workspace?: string + /** * Update snapshot * @@ -721,9 +752,14 @@ export interface UserConfig extends InlineConfig { * Name of the project or projects to run. */ project?: string | string[] + + /** + * Additional exclude patterns + */ + cliExclude?: string[] } -export interface ResolvedConfig extends Omit, 'config' | 'filters' | 'browser' | 'coverage' | 'testNamePattern' | 'related' | 'api' | 'reporters' | 'resolveSnapshotPath' | 'benchmark' | 'shard' | 'cache' | 'sequence' | 'typecheck' | 'runner' | 'poolOptions' | 'pool'> { +export interface ResolvedConfig extends Omit, 'config' | 'filters' | 'browser' | 'coverage' | 'testNamePattern' | 'related' | 'api' | 'reporters' | 'resolveSnapshotPath' | 'benchmark' | 'shard' | 'cache' | 'sequence' | 'typecheck' | 'runner' | 'poolOptions' | 'pool' | 'cliExclude'> { mode: VitestRunMode base?: string @@ -745,6 +781,7 @@ export interface ResolvedConfig extends Omit, 'config' | 'f defines: Record api?: ApiConfig + cliExclude?: string[] benchmark?: Required> & Pick shard?: { diff --git a/packages/web-worker/package.json b/packages/web-worker/package.json index d38eee04ec7b..cf385875755e 100644 --- a/packages/web-worker/package.json +++ b/packages/web-worker/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/web-worker", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "Web Worker support for testing in Vitest", "license": "MIT", "funding": "https://opencollective.com/vitest", @@ -46,7 +46,7 @@ }, "devDependencies": { "@types/debug": "^4.1.12", - "@types/ungap__structured-clone": "^0.3.2", + "@types/ungap__structured-clone": "^0.3.3", "@ungap/structured-clone": "^1.2.0" } } diff --git a/packages/ws-client/package.json b/packages/ws-client/package.json index 5397a5ce66c8..4577f90657e2 100644 --- a/packages/ws-client/package.json +++ b/packages/ws-client/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/ws-client", "type": "module", - "version": "1.0.4", + "version": "1.1.0", "description": "WebSocket client wrapper for communicating with Vitest", "author": "Anthony Fu ", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91ffce256f92..5126ab2de462 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,8 +54,8 @@ importers: specifier: workspace:* version: link:packages/ui bumpp: - specifier: ^9.2.0 - version: 9.2.0 + specifier: ^9.2.1 + version: 9.2.1 esbuild: specifier: ^0.19.5 version: 0.19.5 @@ -118,11 +118,11 @@ importers: version: 3.3.8(typescript@5.2.2) devDependencies: '@iconify-json/carbon': - specifier: ^1.1.24 - version: 1.1.24 + specifier: ^1.1.27 + version: 1.1.27 '@iconify-json/logos': - specifier: ^1.1.40 - version: 1.1.40 + specifier: ^1.1.41 + version: 1.1.41 '@unocss/reset': specifier: ^0.57.4 version: 0.57.4 @@ -1130,8 +1130,8 @@ importers: specifier: ^8.2.0 version: 8.2.0 '@iconify-json/logos': - specifier: ^1.1.40 - version: 1.1.40 + specifier: ^1.1.41 + version: 1.1.41 '@testing-library/cypress': specifier: ^10.0.1 version: 10.0.1(cypress@13.6.0) @@ -1355,8 +1355,8 @@ importers: specifier: ^4.0.6 version: 4.0.6 '@types/prompts': - specifier: ^2.4.8 - version: 2.4.8 + specifier: ^2.4.9 + version: 2.4.9 '@types/sinonjs__fake-timers': specifier: ^8.1.5 version: 8.1.5 @@ -1425,8 +1425,8 @@ importers: specifier: ^4.1.12 version: 4.1.12 '@types/ungap__structured-clone': - specifier: ^0.3.2 - version: 0.3.2 + specifier: ^0.3.3 + version: 0.3.3 '@ungap/structured-clone': specifier: ^1.2.0 version: 1.2.0 @@ -1537,6 +1537,15 @@ importers: specifier: workspace:* version: link:../../packages/vitest + test/cli: + devDependencies: + vite: + specifier: ^5.0.0 + version: 5.0.2(@types/node@18.18.9)(less@4.1.3) + vitest: + specifier: workspace:* + version: link:../../packages/vitest + test/config: devDependencies: vite: @@ -1906,12 +1915,15 @@ importers: test/ui: devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 - playwright-chromium: + '@playwright/test': specifier: ^1.39.0 version: 1.39.0 + '@testing-library/dom': + specifier: ^9.3.3 + version: 9.3.3 + happy-dom: + specifier: latest + version: 12.10.3 vitest: specifier: workspace:* version: link:../../packages/vitest @@ -5511,14 +5523,14 @@ packages: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true - /@iconify-json/carbon@1.1.24: - resolution: {integrity: sha512-Sx4vj3HfQj3yP6a4QzWc1BymDO5uTOGTHeb5it/xaMa196C6+RegNUv1F+Y1h8AJ2Sv93GMI+PyMH0HyqTHEmg==} + /@iconify-json/carbon@1.1.27: + resolution: {integrity: sha512-tJVXv9+D9cjU5HcaY+8J0awv9AL/Mjo9MWR/fxHfHFPP/iokjPBEgq4jOBDGNe8W0k/BTrVI3zpgZjLoi6RNGg==} dependencies: '@iconify/types': 2.0.0 dev: true - /@iconify-json/logos@1.1.40: - resolution: {integrity: sha512-F8XapUfZHp9Q53aFvhwFrwmM1NIXOtDV9G0WxIONSu3diiieObmiJQy5oNxla4LgPccyYYHyJQCo0Diys3sIUA==} + /@iconify-json/logos@1.1.41: + resolution: {integrity: sha512-nq69aemSNSxsPh6hDx6QHQBZca6vQzTg6fUXdo996IQe41TNueyJrQXjdhaZr0jaqGEforuSPGLB16SMSZ+T2A==} dependencies: '@iconify/types': 2.0.0 dev: true @@ -8386,8 +8398,8 @@ packages: dependencies: '@storybook/client-logger': 6.5.10 '@storybook/instrumenter': 6.5.10(react-dom@17.0.2)(react@17.0.2) - '@testing-library/dom': 8.17.1 - '@testing-library/user-event': 13.5.0(@testing-library/dom@8.17.1) + '@testing-library/dom': 8.19.0 + '@testing-library/user-event': 13.5.0(@testing-library/dom@8.19.0) ts-dedent: 2.2.0 transitivePeerDependencies: - react @@ -8640,20 +8652,6 @@ packages: cypress: 13.6.0 dev: true - /@testing-library/dom@8.17.1: - resolution: {integrity: sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==} - engines: {node: '>=12'} - dependencies: - '@babel/code-frame': 7.22.13 - '@babel/runtime': 7.23.2 - '@types/aria-query': 4.2.2 - aria-query: 5.3.0 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.4.4 - pretty-format: 27.5.1 - dev: true - /@testing-library/dom@8.19.0: resolution: {integrity: sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A==} engines: {node: '>=12'} @@ -8664,21 +8662,7 @@ packages: aria-query: 5.3.0 chalk: 4.1.2 dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - dev: true - - /@testing-library/dom@9.3.1: - resolution: {integrity: sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==} - engines: {node: '>=14'} - dependencies: - '@babel/code-frame': 7.22.13 - '@babel/runtime': 7.23.2 - '@types/aria-query': 5.0.1 - aria-query: 5.1.3 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 + lz-string: 1.4.4 pretty-format: 27.5.1 dev: true @@ -8758,7 +8742,7 @@ packages: react-dom: <18.0.0 dependencies: '@babel/runtime': 7.18.9 - '@testing-library/dom': 8.17.1 + '@testing-library/dom': 8.19.0 '@types/react-dom': 17.0.17 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -8772,7 +8756,7 @@ packages: react-dom: ^18.0.0 dependencies: '@babel/runtime': 7.18.9 - '@testing-library/dom': 8.17.1 + '@testing-library/dom': 8.19.0 '@types/react-dom': 18.0.6 react: 18.0.0 react-dom: 18.0.0(react@18.0.0) @@ -8786,7 +8770,7 @@ packages: react-dom: ^18.0.0 dependencies: '@babel/runtime': 7.18.9 - '@testing-library/dom': 8.17.1 + '@testing-library/dom': 8.19.0 '@types/react-dom': 18.0.6 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -8800,7 +8784,7 @@ packages: react-dom: ^18.0.0 dependencies: '@babel/runtime': 7.18.9 - '@testing-library/dom': 8.17.1 + '@testing-library/dom': 8.19.0 '@types/react-dom': 18.0.8 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -8826,18 +8810,18 @@ packages: peerDependencies: svelte: ^3 || ^4 dependencies: - '@testing-library/dom': 9.3.1 + '@testing-library/dom': 9.3.3 svelte: 4.1.1 dev: true - /@testing-library/user-event@13.5.0(@testing-library/dom@8.17.1): + /@testing-library/user-event@13.5.0(@testing-library/dom@8.19.0): resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==} engines: {node: '>=10', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' dependencies: '@babel/runtime': 7.23.2 - '@testing-library/dom': 8.17.1 + '@testing-library/dom': 8.19.0 dev: true /@testing-library/user-event@14.4.3(@testing-library/dom@9.3.3): @@ -8876,10 +8860,6 @@ packages: resolution: {integrity: sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==} dev: true - /@types/aria-query@5.0.1: - resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} - dev: true - /@types/aria-query@5.0.3: resolution: {integrity: sha512-0Z6Tr7wjKJIk4OUEjVUQMtyunLDy339vcMaj38Kpj6jM2OE1p3S4kXExKZ7a3uXQAPCoy3sbrP1wibDKaf39oA==} dev: true @@ -9289,8 +9269,8 @@ packages: resolution: {integrity: sha512-vyv9knII8XeW8TnXDcGH7HqG6FeR56ESN6ExM34d/U8Zvs3xuG34euV6CVyB7KEYI7Ts4lQM8b4NL72e7UadnA==} dev: true - /@types/prompts@2.4.8: - resolution: {integrity: sha512-fPOEzviubkEVCiLduO45h+zFHB0RZX8tFt3C783sO5cT7fUXf3EEECpD26djtYdh4Isa9Z9tasMQuZnYPtvYzw==} + /@types/prompts@2.4.9: + resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} dependencies: '@types/node': 18.18.9 kleur: 3.0.3 @@ -9500,8 +9480,8 @@ packages: source-map: 0.6.1 dev: true - /@types/ungap__structured-clone@0.3.2: - resolution: {integrity: sha512-a7oBPz4/IurTfw0/+R4F315npapBXlSimrQlmDfr0lo1Pv0BeHNADgbHXdDP8LCjnCiRne4jRSr/5UnQitX2og==} + /@types/ungap__structured-clone@0.3.3: + resolution: {integrity: sha512-RNmhIPwoip6K/zZOv3ypksTAqaqLEXvlNSXKyrC93xMSOAHZCR7PifW6xKZCwkbbnbM9dwB9X56PPoNTlNwEqw==} dev: true /@types/unist@2.0.6: @@ -12257,8 +12237,8 @@ packages: semver: 7.5.4 dev: true - /bumpp@9.2.0: - resolution: {integrity: sha512-pgp7y3jp33QTaXFVDrE0IKuZF5Y8EsIz+ywZXFALW2nD+ZD+4crxJe/GypBQBoJuZrr5dc6TGrR3wl7fk3+C6w==} + /bumpp@9.2.1: + resolution: {integrity: sha512-mq6/e8+bnIsOMy1VceTLC49WucMIZqd2nYn0e7Et5LhTO3yYQ8OWJsTl/B+uJDs5eywZmJ4Yt1WTEd2HCI35pw==} engines: {node: '>=10'} hasBin: true dependencies: @@ -12370,7 +12350,7 @@ packages: promise-inflight: 1.0.1(bluebird@3.7.2) rimraf: 3.0.2 ssri: 8.0.1 - tar: 6.1.13 + tar: 6.2.0 unique-filename: 1.1.1 transitivePeerDependencies: - bluebird @@ -20051,11 +20031,6 @@ packages: yallist: 4.0.0 dev: true - /minipass@4.2.5: - resolution: {integrity: sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==} - engines: {node: '>=8'} - dev: true - /minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} @@ -21404,15 +21379,6 @@ packages: mlly: 1.4.2 pathe: 1.1.1 - /playwright-chromium@1.39.0: - resolution: {integrity: sha512-0WVmvn9ppPbcyb2PQherIpzsvJlyjqziCZiAiexTEYSz8k6/+/3wljmFaMRMP1lcv2xKyHDn9yWd/lHb7IzYyA==} - engines: {node: '>=16'} - hasBin: true - requiresBuild: true - dependencies: - playwright-core: 1.39.0 - dev: true - /playwright-core@1.39.0: resolution: {integrity: sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==} engines: {node: '>=16'} @@ -24436,18 +24402,6 @@ packages: streamx: 2.15.1 dev: true - /tar@6.1.13: - resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==} - engines: {node: '>=10'} - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 4.2.5 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - dev: true - /tar@6.2.0: resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} engines: {node: '>=10'} diff --git a/test/cli/fixtures/exclude/math.test.ts b/test/cli/fixtures/exclude/math.test.ts new file mode 100644 index 000000000000..597efcfefb99 --- /dev/null +++ b/test/cli/fixtures/exclude/math.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from 'vitest' + +import { add } from './math' + +test('should add two numbers correctly', () => { + expect(add(1, 2)).toBe(3) +}) diff --git a/test/cli/fixtures/exclude/math.ts b/test/cli/fixtures/exclude/math.ts new file mode 100644 index 000000000000..a39d4ef196bc --- /dev/null +++ b/test/cli/fixtures/exclude/math.ts @@ -0,0 +1,3 @@ +export function add(a: number, b: number): number { + return a + b +} diff --git a/test/cli/fixtures/exclude/string.test.ts b/test/cli/fixtures/exclude/string.test.ts new file mode 100644 index 000000000000..f45777d7866a --- /dev/null +++ b/test/cli/fixtures/exclude/string.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from 'vitest' + +import { capitalize } from './string' + +test('should capitalize strings correctly', () => { + expect(capitalize('i Love Vitest')).toBe('I love vitest') +}) diff --git a/test/cli/fixtures/exclude/string.ts b/test/cli/fixtures/exclude/string.ts new file mode 100644 index 000000000000..c8f32ebe25b8 --- /dev/null +++ b/test/cli/fixtures/exclude/string.ts @@ -0,0 +1,3 @@ +export function capitalize(str: string): string { + return str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase() +} diff --git a/test/ui/fixtures/vitest.config.ts b/test/cli/fixtures/exclude/vitest.exclude.config.ts similarity index 56% rename from test/ui/fixtures/vitest.config.ts rename to test/cli/fixtures/exclude/vitest.exclude.config.ts index 1b3a79748ed4..a76ac25d9e36 100644 --- a/test/ui/fixtures/vitest.config.ts +++ b/test/cli/fixtures/exclude/vitest.exclude.config.ts @@ -1,4 +1,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ + test: { + include: ['fixtures/exclude/*.test.ts'], + }, }) diff --git a/test/cli/package.json b/test/cli/package.json new file mode 100644 index 000000000000..7ed0d533eebb --- /dev/null +++ b/test/cli/package.json @@ -0,0 +1,12 @@ +{ + "name": "@vitest/test-cli", + "type": "module", + "private": true, + "scripts": { + "test": "vitest --exclude fixtures/exclude/**/string.test.ts" + }, + "devDependencies": { + "vite": "latest", + "vitest": "workspace:*" + } +} diff --git a/test/cli/test/exclude.test.ts b/test/cli/test/exclude.test.ts new file mode 100644 index 000000000000..8ef73969d360 --- /dev/null +++ b/test/cli/test/exclude.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest' + +import { runVitestCli } from '../../test-utils' + +test('should still test math.test.ts', async () => { + const { stderr, stdout } = await runVitestCli( + 'run', + '--config', + 'fixtures/exclude/vitest.exclude.config.ts', + '--exclude', + 'fixtures/exclude/string.test.ts', + ) + + expect(stdout).toContain(`✓ fixtures/exclude/math.test.ts`) + expect(stdout).not.toContain(`string.test.ts`) + expect(stderr).toBe('') +}) diff --git a/test/cli/vitest.config.ts b/test/cli/vitest.config.ts new file mode 100644 index 000000000000..d3d5f5dc2452 --- /dev/null +++ b/test/cli/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + test: { + include: ['test/**.test.ts'], + reporters: ['verbose'], + testTimeout: 60_000, + chaiConfig: { + truncateThreshold: 999, + }, + }, +}) diff --git a/test/config/fixtures/workspace-flags/projects/cli-config.test.ts b/test/config/fixtures/workspace-flags/projects/cli-config.test.ts new file mode 100644 index 000000000000..c069dce322ae --- /dev/null +++ b/test/config/fixtures/workspace-flags/projects/cli-config.test.ts @@ -0,0 +1,5 @@ +import { it, expect } from 'vitest'; + +it('math', () => { + expect(1).toBe(1) +}) diff --git a/test/config/fixtures/workspace-flags/projects/vitest.config.js b/test/config/fixtures/workspace-flags/projects/vitest.config.js new file mode 100644 index 000000000000..56004c9f9e06 --- /dev/null +++ b/test/config/fixtures/workspace-flags/projects/vitest.config.js @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/test/config/fixtures/workspace-flags/vitest.workspace.js b/test/config/fixtures/workspace-flags/vitest.workspace.js new file mode 100644 index 000000000000..47d013c1f23e --- /dev/null +++ b/test/config/fixtures/workspace-flags/vitest.workspace.js @@ -0,0 +1 @@ +export default ['projects'] \ No newline at end of file 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/package.json b/test/config/package.json index 3318db3a6246..598c81fc3ec0 100644 --- a/test/config/package.json +++ b/test/config/package.json @@ -3,7 +3,7 @@ "type": "module", "private": true, "scripts": { - "test": "vitest run --typecheck" + "test": "vitest run --typecheck.enabled" }, "devDependencies": { "vite": "latest", diff --git a/test/config/test/config-types.test-d.ts b/test/config/test/config-types.test-d.ts index ce7dbc8288d8..2e3258cfff57 100644 --- a/test/config/test/config-types.test-d.ts +++ b/test/config/test/config-types.test-d.ts @@ -1,5 +1,5 @@ -import { describe, expectTypeOf, test } from 'vitest' -import { type UserWorkspaceConfig, defineConfig, defineProject, defineWorkspace, mergeConfig } from 'vitest/config' +import { assertType, describe, expectTypeOf, test } from 'vitest' +import { defineConfig, defineProject, defineWorkspace, mergeConfig } from 'vitest/config' const expectMainTestConfig = expectTypeOf(defineConfig).parameter(0).resolves.toHaveProperty('test').exclude() const expectProjectTestConfig = expectTypeOf(defineProject).parameter(0).resolves.toHaveProperty('test').exclude() @@ -26,9 +26,76 @@ describe('merge config helper', () => { }) }) -describe('workspace config', () => { - test('correctly defines return type', () => { - expectTypeOf(defineWorkspace([{ test: { name: 'test' } }])).items.toMatchTypeOf() - expectTypeOf(defineWorkspace(['packages/*'])).items.toBeString() +describe('define workspace helper', () => { + type DefineWorkspaceParameter = Parameters[0] + + test('allows string', () => { + assertType(['./path/to/workspace']) + }) + + test('allows config object', () => { + assertType([{ + test: { + name: 'Workspace Project #1', + include: ['string'], + + // @ts-expect-error -- Not allowed here + coverage: {}, + }, + }]) + }) + + test('allows mixing strings and config objects', () => { + assertType([ + './path/to/project', + { + test: { + name: 'Workspace Project #1', + include: ['string'], + + // @ts-expect-error -- Not allowed here + coverage: {}, + }, + }, + './path/to/another/project', + { + extends: 'workspace custom field', + test: { + name: 'Workspace Project #2', + include: ['string'], + + // @ts-expect-error -- Not allowed here + coverage: {}, + }, + }, + ]) + }) + + test('return type matches parameters', () => { + expectTypeOf(defineWorkspace).returns.toMatchTypeOf() + + expectTypeOf(defineWorkspace([ + './path/to/project', + { + test: { + name: 'Workspace Project #1', + include: ['string'], + + // @ts-expect-error -- Not allowed here + coverage: {}, + }, + }, + './path/to/another/project', + { + extends: 'workspace custom field', + test: { + name: 'Workspace Project #2', + include: ['string'], + + // @ts-expect-error -- Not allowed here + coverage: {}, + }, + }, + ])).items.toMatchTypeOf() }) }) diff --git a/test/config/test/failures.test.ts b/test/config/test/failures.test.ts index ee57b0b168df..aa9c688a5207 100644 --- a/test/config/test/failures.test.ts +++ b/test/config/test/failures.test.ts @@ -33,19 +33,19 @@ test('shard index must be smaller than count', async () => { test('inspect requires changing pool and singleThread/singleFork', async () => { const { stderr } = await runVitest({ inspect: true }) - expect(stderr).toMatch('Error: You cannot use --inspect without "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"') + expect(stderr).toMatch('Error: You cannot use --inspect without "--no-parallelism", "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"') }) test('inspect cannot be used with multi-threading', async () => { const { stderr } = await runVitest({ inspect: true, pool: 'threads', poolOptions: { threads: { singleThread: false } } }) - expect(stderr).toMatch('Error: You cannot use --inspect without "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"') + expect(stderr).toMatch('Error: You cannot use --inspect without "--no-parallelism", "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"') }) test('inspect-brk cannot be used with multi processing', async () => { const { stderr } = await runVitest({ inspect: true, pool: 'forks', poolOptions: { forks: { singleFork: false } } }) - expect(stderr).toMatch('Error: You cannot use --inspect without "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"') + expect(stderr).toMatch('Error: You cannot use --inspect without "--no-parallelism", "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"') }) test('c8 coverage provider is not supported', async () => { diff --git a/test/config/test/flags.test.ts b/test/config/test/flags.test.ts new file mode 100644 index 000000000000..ab7230acc25e --- /dev/null +++ b/test/config/test/flags.test.ts @@ -0,0 +1,38 @@ +import { expect, it } from 'vitest' +import { runVitest } from '../../test-utils' + +it('correctly inherit from the cli', async () => { + const { vitest } = await runVitest({ + root: 'fixtures/workspace-flags', + logHeapUsage: true, + allowOnly: true, + sequence: { + seed: 123, + }, + testTimeout: 5321, + pool: 'forks', + globals: true, + expandSnapshotDiff: true, + retry: 6, + testNamePattern: 'math', + passWithNoTests: true, + bail: 100, + }) + const project = vitest!.projects[0] + const config = project.getSerializableConfig() + expect(config).toMatchObject({ + logHeapUsage: true, + allowOnly: true, + sequence: expect.objectContaining({ + seed: 123, + }), + testTimeout: 5321, + pool: 'forks', + globals: true, + expandSnapshotDiff: true, + retry: 6, + passWithNoTests: true, + bail: 100, + }) + expect(config.testNamePattern?.test('math')).toBe(true) +}) diff --git a/test/config/test/resolution.test.ts b/test/config/test/resolution.test.ts new file mode 100644 index 000000000000..a222e7812692 --- /dev/null +++ b/test/config/test/resolution.test.ts @@ -0,0 +1,104 @@ +import type { UserConfig } from 'vitest' +import { describe, expect, it } from 'vitest' +import { createVitest } from 'vitest/node' + +async function config(cliOptions: UserConfig, configValue: UserConfig = {}) { + const vitest = await createVitest('test', { ...cliOptions, watch: false }, { test: configValue }) + return vitest.config +} + +describe('correctly defines isolated flags', async () => { + it('prefers CLI poolOptions flags over config', async () => { + const c = await config({ + isolate: true, + poolOptions: { + threads: { + isolate: false, + }, + forks: { + isolate: false, + }, + }, + }) + expect(c.poolOptions?.threads?.isolate).toBe(false) + expect(c.poolOptions?.forks?.isolate).toBe(false) + expect(c.isolate).toBe(true) + }) + + it('override CLI poolOptions flags over isolate', async () => { + const c = await config({ + isolate: false, + poolOptions: { + threads: { + isolate: true, + }, + forks: { + isolate: true, + }, + }, + }, { + poolOptions: { + threads: { + isolate: false, + }, + forks: { + isolate: false, + }, + }, + }) + expect(c.poolOptions?.threads?.isolate).toBe(true) + expect(c.poolOptions?.forks?.isolate).toBe(true) + expect(c.isolate).toBe(false) + }) + + it('override CLI isolate flag if poolOptions is not set via CLI', async () => { + const c = await config({ + isolate: true, + }, { + poolOptions: { + threads: { + isolate: false, + }, + forks: { + isolate: false, + }, + }, + }) + expect(c.poolOptions?.threads?.isolate).toBe(true) + expect(c.poolOptions?.forks?.isolate).toBe(true) + expect(c.isolate).toBe(true) + }) + + it('keeps user configured poolOptions if no CLI flag is provided', async () => { + const c = await config({}, { + poolOptions: { + threads: { + isolate: false, + }, + forks: { + isolate: false, + }, + }, + }) + expect(c.poolOptions?.threads?.isolate).toBe(false) + expect(c.poolOptions?.forks?.isolate).toBe(false) + expect(c.isolate).toBe(true) + }) + + it('isolate config value overrides poolOptions defaults', async () => { + const c = await config({}, { + isolate: false, + }) + expect(c.poolOptions?.threads?.isolate).toBe(false) + expect(c.poolOptions?.forks?.isolate).toBe(false) + expect(c.isolate).toBe(false) + }) + + it('if no isolation is defined in the config, fallback ot undefined', async () => { + const c = await config({}, {}) + expect(c.poolOptions?.threads?.isolate).toBe(undefined) + expect(c.poolOptions?.forks?.isolate).toBe(undefined) + // set in configDefaults, so it's always defined + expect(c.isolate).toBe(true) + }) +}) 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/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 4f78f9b8e1cd..492678750e72 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -903,4 +903,96 @@ it('correctly prints diff with asymmetric matchers', () => { } }) +it('toHaveProperty error diff', () => { + setupColors(getDefaultColors()) + + // make it easy for dev who trims trailing whitespace on IDE + function trim(s: string): string { + return s.replaceAll(/ *$/gm, '') + } + + function getError(f: () => unknown) { + try { + f() + return expect.unreachable() + } + catch (error) { + const processed = processError(error) + return [processed.message, trim(processed.diff)] + } + } + + // non match value + expect(getError(() => expect({ name: 'foo' }).toHaveProperty('name', 'bar'))).toMatchInlineSnapshot(` + [ + "expected { name: 'foo' } to have property "name" with value 'bar'", + "- Expected + + Received + + - bar + + foo", + ] + `) + + // non match key + expect(getError(() => expect({ noName: 'foo' }).toHaveProperty('name', 'bar'))).toMatchInlineSnapshot(` + [ + "expected { noName: 'foo' } to have property "name" with value 'bar'", + "- Expected: + "bar" + + + Received: + undefined", + ] + `) + + // non match value (with asymmetric matcher) + expect(getError(() => expect({ name: 'foo' }).toHaveProperty('name', expect.any(Number)))).toMatchInlineSnapshot(` + [ + "expected { name: 'foo' } to have property "name" with value Any{ …(3) }", + "- Expected: + Any + + + Received: + "foo"", + ] + `) + + // non match key (with asymmetric matcher) + expect(getError(() => expect({ noName: 'foo' }).toHaveProperty('name', expect.any(Number)))).toMatchInlineSnapshot(` + [ + "expected { noName: 'foo' } to have property "name" with value Any{ …(3) }", + "- Expected: + Any + + + Received: + undefined", + ] + `) + + // non match value (deep key) + expect(getError(() => expect({ parent: { name: 'foo' } }).toHaveProperty('parent.name', 'bar'))).toMatchInlineSnapshot(` + [ + "expected { parent: { name: 'foo' } } to have property "parent.name" with value 'bar'", + "- Expected + + Received + + - bar + + foo", + ] + `) + + // non match key (deep key) + expect(getError(() => expect({ parent: { noName: 'foo' } }).toHaveProperty('parent.name', 'bar'))).toMatchInlineSnapshot(` + [ + "expected { parent: { noName: 'foo' } } to have property "parent.name" with value 'bar'", + "- Expected: + "bar" + + + Received: + undefined", + ] + `) +}) + it('timeout', () => new Promise(resolve => setTimeout(resolve, 500))) diff --git a/test/fails/fixtures/test-extend/fixture-error.test.ts b/test/fails/fixtures/test-extend/fixture-error.test.ts index 4f815b9cae8d..2ec15cf73b8f 100644 --- a/test/fails/fixtures/test-extend/fixture-error.test.ts +++ b/test/fails/fixtures/test-extend/fixture-error.test.ts @@ -50,3 +50,16 @@ describe('correctly fails when test times out', () => { expect(a).toBe(2) }, 20) }) + +describe('error thrown during fixture teardown', () => { + const myTest = test.extend<{ a: string }>({ + a: async ({}, use) => { + await use("hello"); + throw new Error('Error fixture teardown') + }, + }) + + myTest('no error in test', ({ a }) => { + expect(a).toBe("hello"); + }) +}) diff --git a/test/fails/test/__snapshots__/runner.test.ts.snap b/test/fails/test/__snapshots__/runner.test.ts.snap index b628660bd975..5ee589c515ee 100644 --- a/test/fails/test/__snapshots__/runner.test.ts.snap +++ b/test/fails/test/__snapshots__/runner.test.ts.snap @@ -43,7 +43,8 @@ TypeError: failure" exports[`should fail test-extend/circular-dependency.test.ts > test-extend/circular-dependency.test.ts 1`] = `"Error: Circular fixture dependency detected: a <- b <- a"`; exports[`should fail test-extend/fixture-error.test.ts > test-extend/fixture-error.test.ts 1`] = ` -"Error: Test timed out in 20ms. +"Error: Error fixture teardown +Error: Test timed out in 20ms. Error: Error thrown in test fixture Error: Error thrown in afterEach fixture Error: Error thrown in beforeEach fixture" diff --git a/test/run-once/vitest.config.ts b/test/run-once/vitest.config.ts index 8d93d710efcb..9ca84b2b94b0 100644 --- a/test/run-once/vitest.config.ts +++ b/test/run-once/vitest.config.ts @@ -2,6 +2,6 @@ import { defineConfig } from 'vite' export default defineConfig({ test: { - poolOptions: { threads: { isolate: false } }, + isolate: false, }, }) diff --git a/test/stacktraces/fixtures/vite.config.ts b/test/stacktraces/fixtures/vite.config.ts index 54b0069ddff0..64d31c38f6d1 100644 --- a/test/stacktraces/fixtures/vite.config.ts +++ b/test/stacktraces/fixtures/vite.config.ts @@ -41,12 +41,8 @@ export default defineConfig({ }, }], test: { + isolate: false, pool: 'forks', - poolOptions: { - forks: { - isolate: false, - }, - }, include: ['**/*.{test,spec}.{imba,?(c|m)[jt]s?(x)}'], setupFiles: ['./setup.js'], }, diff --git a/test/ui/.gitignore b/test/ui/.gitignore index ad7fcab32638..bcb45cf2fa3c 100644 --- a/test/ui/.gitignore +++ b/test/ui/.gitignore @@ -1 +1,2 @@ -fixtures/html +html +test-results diff --git a/test/ui/fixtures/console.test.ts b/test/ui/fixtures/console.test.ts new file mode 100644 index 000000000000..2550b9a32503 --- /dev/null +++ b/test/ui/fixtures/console.test.ts @@ -0,0 +1,33 @@ +/* eslint-disable no-console */ + +import { it } from "vitest"; +import { prettyDOM } from "@testing-library/dom" + +// https://github.com/vitest-dev/vitest/issues/2765 +it('regexp', () => { + console.log(/(?\w)/) +}) + +// https://github.com/vitest-dev/vitest/issues/3934 +it('html-raw', async () => { + console.log(` +
+ + + +
+`); +}) + +// https://github.com/vitest-dev/vitest/issues/1279 +it('html-pretty', () => { + const div = document.createElement("div"); + div.innerHTML = ` +
+ + + +
+ `.replaceAll(/\n */gm, ""); // strip new liens + console.log(prettyDOM(div)) +}) diff --git a/test/ui/fixtures/coverage.test.ts b/test/ui/fixtures/coverage.test.ts new file mode 100644 index 000000000000..802168139d65 --- /dev/null +++ b/test/ui/fixtures/coverage.test.ts @@ -0,0 +1,6 @@ +import { expect, it } from 'vitest' +import { multiply } from './coverage' + +it(multiply, () => { + expect(multiply(2, 3)).toEqual(6) +}) diff --git a/test/ui/fixtures/coverage.ts b/test/ui/fixtures/coverage.ts new file mode 100644 index 000000000000..1f2e9649ed2f --- /dev/null +++ b/test/ui/fixtures/coverage.ts @@ -0,0 +1,3 @@ +export function multiply(n: number, m: number) { + return n * m; +} diff --git a/test/ui/globalSetup.ts b/test/ui/globalSetup.ts deleted file mode 100644 index 1e820e5882bd..000000000000 --- a/test/ui/globalSetup.ts +++ /dev/null @@ -1,21 +0,0 @@ -import os from 'node:os' -import path from 'node:path' -import { existsSync, promises as fsp } from 'node:fs' -import type { BrowserServer } from 'playwright-chromium' -import { chromium } from 'playwright-chromium' - -const DIR = path.join(os.tmpdir(), 'vitest_playwright_global_setup') - -let browserServer: BrowserServer | undefined - -export async function setup(): Promise { - browserServer = await chromium.launchServer() - - if (!existsSync(DIR)) - await fsp.mkdir(DIR, { recursive: true }) - await fsp.writeFile(path.join(DIR, 'wsEndpoint'), browserServer.wsEndpoint()) -} - -export async function teardown(): Promise { - await browserServer?.close() -} diff --git a/test/ui/package.json b/test/ui/package.json index ee433096c7e1..4da03349c435 100644 --- a/test/ui/package.json +++ b/test/ui/package.json @@ -3,11 +3,13 @@ "type": "module", "private": true, "scripts": { - "test": "vitest run" + "test-e2e": "playwright test", + "test-fixtures": "vitest" }, "devDependencies": { - "execa": "^6.1.0", - "playwright-chromium": "^1.39.0", + "@playwright/test": "^1.39.0", + "@testing-library/dom": "^9.3.3", + "happy-dom": "latest", "vitest": "workspace:*" } } diff --git a/test/ui/playwright.config.ts b/test/ui/playwright.config.ts new file mode 100644 index 000000000000..ee92ab48505c --- /dev/null +++ b/test/ui/playwright.config.ts @@ -0,0 +1,11 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './test', + projects: [ + { + name: 'chromium', + use: devices['Desktop Chrome'], + }, + ], +}) diff --git a/test/ui/setup.ts b/test/ui/setup.ts deleted file mode 100644 index 62d20c407c59..000000000000 --- a/test/ui/setup.ts +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable import/no-mutable-exports */ -import path from 'node:path' -import os from 'node:os' -import { readFileSync } from 'node:fs' -import { chromium } from 'playwright-chromium' -import type { Browser, Page } from 'playwright-chromium' -import { expect } from 'vitest' -import type { ExecaChildProcess } from 'execa' -import { execaCommand } from 'execa' - -export let page!: Page -export let browser!: Browser -export const browserErrors: Error[] = [] -export const ports = { - ui: 9000, - report: 9001, -} - -const DIR = path.join(os.tmpdir(), 'vitest_playwright_global_setup') -export const isWindows = process.platform === 'win32' - -export function timeout(time: number) { - return new Promise((resolve) => { - setTimeout(() => { - resolve(null) - }, time) - }) -} - -export async function withRetry( - func: () => Promise, -): Promise { - const maxTries = 300 - for (let tries = 0; tries < maxTries; tries++) { - try { - await func() - return - } - catch {} - await timeout(50) - } - await func() -} - -export async function withLoadUrl(url: string): Promise { - return withRetry(async () => { - const res = await fetch(url) - if (!res.ok) - throw new Error('url not loaded') - }) -} - -export async function killProcess( - serverProcess: ExecaChildProcess, -): Promise { - if (isWindows) { - try { - const { execaCommandSync } = await import('execa') - execaCommandSync(`taskkill /pid ${serverProcess.pid} /T /F`) - } - catch (e) { - console.error('failed to taskkill:', e) - } - } - else { - serverProcess.kill('SIGTERM', { forceKillAfterTimeout: 2000 }) - } -} - -export async function startChromium() { - const wsEndpoint = readFileSync(path.join(DIR, 'wsEndpoint'), 'utf-8') - if (!wsEndpoint) - throw new Error('wsEndpoint not found') - - browser = await chromium.connect(wsEndpoint) - page = await browser.newPage() - - try { - page.on('pageerror', (error) => { - browserErrors.push(error) - }) - } - catch (e) { - await page.close() - throw e - } - - return async () => { - await page?.close() - if (browser) - await browser.close() - } -} - -export async function startServerCommand(command: string, url: string) { - let error: any - const exitChromium = await startChromium() - const subProcess = execaCommand(command, { - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - stdio: 'pipe', - }) - - subProcess.catch((e) => { - error = e - }) - - const killSubProcess = () => killProcess(subProcess) - - subProcess.stdout?.on('data', (d) => { - // eslint-disable-next-line no-console - console.log(d.toString()) - }) - - expect(error).not.toBeTruthy() - - async function exit() { - try { - await killSubProcess() - await exitChromium() - } - catch (e) { - console.error( - `error while killing process ${command}:`, - e, - ) - } - } - - try { - await withLoadUrl(url) - await page.goto(url) - } - catch (e) { - await exit() - throw e - } - - return exit -} - -/** - * Poll a getter until the value it returns includes the expected value. - */ -export async function untilUpdated( - poll: () => string | Promise | null, - expected: string, -): Promise { - const maxTries = process.env.CI ? 200 : 50 - for (let tries = 0; tries < maxTries; tries++) { - const actual = (await poll()) ?? '' - if (actual.includes(expected) || tries === maxTries - 1) { - expect(actual).toMatch(expected) - break - } - else { - await timeout(50) - } - } -} diff --git a/test/ui/shim.d.ts b/test/ui/shim.d.ts deleted file mode 100644 index fbc5dbe185b9..000000000000 --- a/test/ui/shim.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module 'kill-port' { - const kill: (port: number) => Promise - export default kill -} diff --git a/test/ui/test/html-report.spec.ts b/test/ui/test/html-report.spec.ts index 162dd8850a68..8c2e094d6525 100644 --- a/test/ui/test/html-report.spec.ts +++ b/test/ui/test/html-report.spec.ts @@ -1,54 +1,58 @@ -import { resolve } from 'node:path' -import { beforeAll, describe, expect, it } from 'vitest' -import { browserErrors, isWindows, page, ports, startServerCommand, untilUpdated } from '../setup' +import { expect, test } from '@playwright/test' +import type { PreviewServer } from 'vite' +import { preview } from 'vite' +import { startVitest } from 'vitest/node' -import { runVitest } from '../../test-utils' +const port = 9001 +const pageUrl = `http://localhost:${port}/` -const root = resolve(__dirname, '../fixtures') -const port = ports.report +test.describe('html report', () => { + let previewServer: PreviewServer -// TODO: fix flakyness on windows -describe.skipIf(isWindows)('html report', () => { - beforeAll(async () => { - await runVitest({ root, reporters: 'html', outputFile: 'html/index.html' }) + test.beforeAll(async () => { + // generate vitest html report + await startVitest('test', [], { run: true, reporters: 'html', coverage: { enabled: true, reportsDirectory: 'html/coverage' } }) - const exit = await startServerCommand( - `pnpm exec vite preview --outDir fixtures/html --strict-port --port ${port}`, - `http://localhost:${port}/`, - ) - - return exit + // run vite preview server + previewServer = await preview({ build: { outDir: 'html' }, preview: { port, strictPort: true } }) }) - it('dashboard', async () => { - await untilUpdated(() => page.textContent('[aria-labelledby]'), '1 Pass 0 Fail 1 Total ') + test.afterAll(async () => { + await new Promise((resolve, reject) => { + previewServer.httpServer.close((err) => { + err ? reject(err) : resolve() + }) + }) }) - describe('file detail', async () => { - beforeAll(async () => { - await page.click('.details-panel span') - }) + test('basic', async ({ page }) => { + const pageErrors: unknown[] = [] + page.on('pageerror', error => pageErrors.push(error)) - it('report', async () => { - await page.click('[data-testid=btn-report]') - await untilUpdated(() => page.textContent('[data-testid=report]'), 'All tests passed in this file') - await untilUpdated(() => page.textContent('[data-testid=filenames]'), 'sample.test.ts') - }) + await page.goto(pageUrl) - it('graph', async () => { - await page.click('[data-testid=btn-graph]') - expect(page.url()).toMatch('graph') - await untilUpdated(() => page.textContent('[data-testid=graph] text'), 'sample.test.ts') - }) + // dashbaord + await expect(page.locator('[aria-labelledby=tests]')).toContainText('5 Pass 0 Fail 5 Total') - it('console', async () => { - await page.click('[data-testid=btn-console]') - expect(page.url()).toMatch('console') - await untilUpdated(() => page.textContent('[data-testid=console] pre'), 'log test') - }) + // report + await page.getByText('sample.test.ts').click() + await page.getByText('All tests passed in this file').click() + await expect(page.getByTestId('filenames')).toContainText('sample.test.ts') + + // graph tab + await page.getByTestId('btn-graph').click() + await expect(page.locator('[data-testid=graph] text')).toContainText('sample.test.ts') + + // console tab + await page.getByTestId('btn-console').click() + await expect(page.getByTestId('console')).toContainText('log test') + + expect(pageErrors).toEqual([]) }) - it('no error happen', () => { - expect(browserErrors.length).toEqual(0) + test('coverage', async ({ page }) => { + await page.goto(pageUrl) + await page.getByLabel('Show coverage').click() + await page.frameLocator('#vitest-ui-coverage').getByRole('heading', { name: 'All files' }).click() }) }) diff --git a/test/ui/test/ui.spec.ts b/test/ui/test/ui.spec.ts index 1995877d3252..770f348da099 100644 --- a/test/ui/test/ui.spec.ts +++ b/test/ui/test/ui.spec.ts @@ -1,48 +1,74 @@ -import { beforeAll, describe, expect, it } from 'vitest' -import { browserErrors, isWindows, page, ports, startServerCommand, untilUpdated } from '../setup' +import { expect, test } from '@playwright/test' +import { type Vitest, startVitest } from 'vitest/node' -const port = ports.ui +const port = 9000 +const pageUrl = `http://localhost:${port}/__vitest__/` -// TODO: fix flakyness on windows -describe.skipIf(isWindows)('ui', () => { - beforeAll(async () => { - const exit = await startServerCommand( - `pnpm exec vitest --root ./fixtures --ui --open false --api.port ${port} --watch --allowOnly`, - `http://localhost:${port}/__vitest__/`, - ) +test.describe('ui', () => { + let vitest: Vitest | undefined - return exit + test.beforeAll(async () => { + vitest = await startVitest('test', [], { watch: true, ui: true, open: false, api: { port }, coverage: { enabled: true } }) + expect(vitest).toBeDefined() }) - it('dashboard', async () => { - await untilUpdated(() => page.textContent('[aria-labelledby]'), '1 Pass 0 Fail 1 Total ') + test.afterAll(async () => { + await vitest?.close() }) - describe('file detail', async () => { - beforeAll(async () => { - await page.click('.details-panel span') - }) - - it('report', async () => { - await page.click('[data-testid=btn-report]') - await untilUpdated(() => page.textContent('[data-testid=report]'), 'All tests passed in this file') - await untilUpdated(() => page.textContent('[data-testid=filenames]'), 'sample.test.ts') - }) - - it('graph', async () => { - await page.click('[data-testid=btn-graph]') - expect(page.url()).toMatch('graph') - await untilUpdated(() => page.textContent('[data-testid=graph] text'), 'sample.test.ts') - }) - - it('console', async () => { - await page.click('[data-testid=btn-console]') - expect(page.url()).toMatch('console') - await untilUpdated(() => page.textContent('[data-testid=console] pre'), 'log test') - }) + test('basic', async ({ page }) => { + const pageErrors: unknown[] = [] + page.on('pageerror', error => pageErrors.push(error)) + + await page.goto(pageUrl) + + // dashbaord + await expect(page.locator('[aria-labelledby=tests]')).toContainText('5 Pass 0 Fail 5 Total') + + // report + await page.getByText('sample.test.ts').click() + await page.getByText('All tests passed in this file').click() + await expect(page.getByTestId('filenames')).toContainText('sample.test.ts') + + // graph tab + await page.getByTestId('btn-graph').click() + await expect(page.locator('[data-testid=graph] text')).toContainText('sample.test.ts') + + // console tab + await page.getByTestId('btn-console').click() + await expect(page.getByTestId('console')).toContainText('log test') + + expect(pageErrors).toEqual([]) }) - it('no error happen', () => { - expect(browserErrors.length).toEqual(0) + test('coverage', async ({ page }) => { + await page.goto(pageUrl) + await page.getByLabel('Show coverage').click() + await page.frameLocator('#vitest-ui-coverage').getByRole('heading', { name: 'All files' }).click() + }) + + test('console', async ({ page }) => { + await page.goto(pageUrl) + await page.getByText('fixtures/console.test.ts').click() + await page.getByTestId('btn-console').click() + await page.getByText('/(?\\w)/').click() + }) + + test('file-filter', async ({ page }) => { + await page.goto(pageUrl) + + // match all files when no filter + await page.getByPlaceholder('Search...').fill('') + await page.getByText('PASS (3)').click() + await expect(page.getByText('fixtures/sample.test.ts', { exact: true })).toBeVisible() + + // match nothing + await page.getByPlaceholder('Search...').fill('nothing') + await page.getByText('No matched test').click() + + // searching "add" will match "sample.test.ts" since it includes a test case named "add" + await page.getByPlaceholder('Search...').fill('add') + await page.getByText('PASS (1)').click() + await expect(page.getByText('fixtures/sample.test.ts', { exact: true })).toBeVisible() }) }) diff --git a/test/ui/vitest.config.ts b/test/ui/vitest.config.ts index 8059722bb55a..47a9163b7cbc 100644 --- a/test/ui/vitest.config.ts +++ b/test/ui/vitest.config.ts @@ -2,10 +2,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - setupFiles: ['./setup.ts'], - globalSetup: ['./globalSetup.ts'], - exclude: ['node_modules', 'fixtures', 'dist'], - hookTimeout: 60_000, - testTimeout: 60_000, + dir: './fixtures', + environment: 'happy-dom', }, }) 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: {