Skip to content

Commit

Permalink
Merge branch 'vitest-dev:main' into fix-override-sequence-concurrent
Browse files Browse the repository at this point in the history
  • Loading branch information
dsyddall authored Oct 10, 2024
2 parents 6c45eaa + 11b9432 commit 2c6702d
Show file tree
Hide file tree
Showing 15 changed files with 2,049 additions and 1,000 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
// { "rule": "*semi", "severity": "off" }
// ],

"vitest.workspaceConfig": "./vitest.workspace.vscode.ts",
"testing.openTesting": "neverOpen",

// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
Expand Down
6 changes: 3 additions & 3 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1650,11 +1650,11 @@ Configure options for Vite server that serves code in the browser. Does not affe
#### browser.provider
- **Type:** `'webdriverio' | 'playwright' | string`
- **Default:** `'webdriverio'`
- **Type:** `'webdriverio' | 'playwright' | 'preview' | string`
- **Default:** `'preview'`
- **CLI:** `--browser.provider=playwright`
Path to a provider that will be used when running browser tests. Vitest provides two providers which are `webdriverio` (default) and `playwright`. Custom providers should be exported using `default` export and have this shape:
Path to a provider that will be used when running browser tests. Vitest provides three providers which are `preview` (default), `webdriverio` and `playwright`. Custom providers should be exported using `default` export and have this shape:
```ts
export interface BrowserProvider {
Expand Down
35 changes: 18 additions & 17 deletions docs/guide/workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ Vitest provides built-in support for monorepos through a workspace configuration

## Defining a Workspace

A workspace should have a `vitest.workspace` or `vitest.projects` file in its root (in the same folder as your config file if you have one). Vitest supports `ts`/`js`/`json` extensions for this file.
A workspace must include a `vitest.workspace` or `vitest.projects` file in its root directory (located in the same folder as your root configuration file, if applicable). Vitest supports `ts`, `js`, and `json` extensions for this file.

Workspace configuration file should have a default export with a list of files or glob patterns referencing your projects. For example, if you have a folder named `packages` that contains your projects, you can define a workspace with this config file:
::: tip NAMING
Please note that this feature is named `workspace`, not `workspaces` (without an "s" at the end).
:::

Workspace configuration file must have a default export with a list of files or glob patterns referencing your projects. For example, if you have a folder named `packages` that contains your projects, you can define a workspace with this config file:

:::code-group
```ts [vitest.workspace.ts]
Expand All @@ -26,10 +30,10 @@ export default [
```
:::

Vitest will consider every folder in `packages` as a separate project even if it doesn't have a config file inside. Since Vitest 2.1, if this glob pattern matches any file it will be considered a Vitest config even if it doesn't have a `vitest` in its name.
Vitest will treat every folder in `packages` as a separate project even if it doesn't have a config file inside. Since Vitest 2.1, if this glob pattern matches any file it will be considered a Vitest config even if it doesn't have a `vitest` in its name.

::: warning
Vitest will not consider the root config as a workspace project (so it will not run tests specified in `include`) unless it is specified in this config.
Vitest does not treat the root `vitest.config` file as a workspace project unless it is explicitly specified in the workspace configuration. Consequently, the root configuration will only influence global options such as `reporters` and `coverage`.
:::

You can also reference projects with their config files:
Expand All @@ -42,9 +46,9 @@ export default [
```
:::

This pattern will only include projects with `vitest.config` file that includes `e2e` and `unit` before the extension.
This pattern will only include projects with a `vitest.config` file that contains `e2e` or `unit` before the extension.

You can also define projects with inline config. Workspace file supports using both syntaxes at the same time.
You can also define projects using inline configuration. The workspace file supports both syntaxes simultaneously.

:::code-group
```ts [vitest.workspace.ts]
Expand Down Expand Up @@ -76,10 +80,10 @@ export default defineWorkspace([
:::

::: warning
All projects should have unique names. Otherwise, Vitest will throw an error. If you do not provide a name inside the inline config, Vitest will assign a number. If you don't provide a name inside a project config defined with glob syntax, Vitest will use the directory name by default.
All projects must have unique names; otherwise, Vitest will throw an error. If a name is not provided in the inline configuration, Vitest will assign a number. For project configurations defined with glob syntax, Vitest will default to using the "name" property in the nearest `package.json` file or the folder name if no such file exists.
:::

If you don't rely on inline configs, you can just create a small json file in your root directory:
If you do not use inline configurations, you can create a small JSON file in your root directory:

:::code-group
```json [vitest.workspace.json]
Expand All @@ -89,7 +93,7 @@ If you don't rely on inline configs, you can just create a small json file in yo
```
:::

Workspace projects don't support all configuration properties. For better type safety, use `defineProject` instead of `defineConfig` method inside project configuration files:
Workspace projects do not support all configuration properties. For better type safety, use the `defineProject` method instead of `defineConfig` within project configuration files:

:::code-group
```ts [packages/a/vitest.config.ts] twoslash
Expand Down Expand Up @@ -191,9 +195,10 @@ export default mergeConfig(
```
:::

At the `defineWorkspace` level you can also use the `extends` option instead to inherit from your root-level config.
At the `defineWorkspace` level, you can use the `extends` option to inherit from your root-level configuration. All options will be merged.

::: code-group
```ts [packages/a/vitest.config.ts]
```ts [vitest.workspace.ts]
import { defineWorkspace } from 'vitest/config'

export default defineWorkspace([
Expand All @@ -215,17 +220,13 @@ export default defineWorkspace([
```
:::

Also, some of the configuration options are not allowed in a project config. Most notably:
Some of the configuration options are not allowed in a project config. Most notably:

- `coverage`: coverage is done for the whole workspace
- `reporters`: only root-level reporters can be supported
- `resolveSnapshotPath`: only root-level resolver is respected
- all other options that don't affect test runners

::: tip
All configuration options that are not supported inside a project config have <NonProjectOption /> sign next them in ["Config"](/config/) page.
All configuration options that are not supported inside a project configuration are marked with a <NonProjectOption /> sign in the ["Config"](/config/) guide.
:::

## Coverage

Coverage for workspace projects works out of the box.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"pnpm": {
"overrides": {
"@vitest/browser": "workspace:*",
"@vitest/ui": "workspace:*",
"acorn": "8.11.3",
"mlly": "^1.7.1",
"rollup": "$rollup",
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/matchers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ declare module 'vitest' {
type PromisifyDomAssertion<T> = Promisify<Assertion<T>>

interface ExpectStatic {
element: <T extends Element | Locator>(element: T, options?: ExpectPollOptions) => PromisifyDomAssertion<Awaited<Element>>
element: <T extends Element | Locator>(element: T, options?: ExpectPollOptions) => PromisifyDomAssertion<Awaited<Element | null>>
}
}

Expand Down
12 changes: 9 additions & 3 deletions packages/browser/src/client/tester/expect-element.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as matchers from '@testing-library/jest-dom/matchers'
import type { Locator } from '@vitest/browser/context'
import type { ExpectPollOptions } from 'vitest'
import { expect } from 'vitest'
import { chai, expect } from 'vitest'

export async function setupExpectDom() {
expect.extend(matchers)
Expand All @@ -10,10 +10,16 @@ export async function setupExpectDom() {
throw new Error(`Invalid element or locator: ${elementOrLocator}. Expected an instance of Element or Locator, received ${typeof elementOrLocator}`)
}

return expect.poll<Element>(() => {
if (elementOrLocator instanceof Element) {
return expect.poll<Element | null>(function element(this: object) {
if (elementOrLocator instanceof Element || elementOrLocator == null) {
return elementOrLocator
}
const isNot = chai.util.flag(this, 'negate')
const name = chai.util.flag(this, '_name')
// special case for `toBeInTheDocument` matcher
if (isNot && name === 'toBeInTheDocument') {
return elementOrLocator.query()
}
return elementOrLocator.element()
}, options)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/expect/src/jest-expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
stringify,
} from './jest-matcher-utils'
import { JEST_MATCHERS_OBJECT } from './constants'
import { recordAsyncExpect, wrapSoft } from './utils'
import { recordAsyncExpect, wrapAssertion } from './utils'

// polyfill globals because expect can be used in node environment
declare class Node {
Expand All @@ -43,7 +43,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
fn: (this: Chai.AssertionStatic & Assertion, ...args: any[]) => any,
) {
const addMethod = (n: keyof Assertion) => {
const softWrapper = wrapSoft(utils, fn)
const softWrapper = wrapAssertion(utils, n, fn)
utils.addMethod(chai.Assertion.prototype, n, softWrapper)
utils.addMethod(
(globalThis as any)[JEST_MATCHERS_OBJECT].matchers,
Expand Down
4 changes: 2 additions & 2 deletions packages/expect/src/jest-extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from './jest-matcher-utils'

import { equals, iterableEquality, subsetEquality } from './jest-utils'
import { wrapSoft } from './utils'
import { wrapAssertion } from './utils'

function getMatcherState(
assertion: Chai.AssertionStatic & Chai.Assertion,
Expand Down Expand Up @@ -96,7 +96,7 @@ function JestExtendPlugin(
}
}

const softWrapper = wrapSoft(utils, expectWrapper)
const softWrapper = wrapAssertion(utils, expectAssertionName, expectWrapper)
utils.addMethod(
(globalThis as any)[JEST_MATCHERS_OBJECT].matchers,
expectAssertionName,
Expand Down
5 changes: 4 additions & 1 deletion packages/expect/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ export function recordAsyncExpect(
return promise
}

export function wrapSoft(
export function wrapAssertion(
utils: Chai.ChaiUtils,
name: string,
fn: (this: Chai.AssertionStatic & Assertion, ...args: any[]) => void,
) {
return function (this: Chai.AssertionStatic & Assertion, ...args: any[]) {
utils.flag(this, '_name', name)

if (!utils.flag(this, 'soft')) {
return fn.apply(this, args)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/vitest/src/integrations/chai/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] {
const assertion = expect(null, message).withContext({
poll: true,
}) as Assertion
fn = fn.bind(assertion)
const proxy: any = new Proxy(assertion, {
get(target, key, receiver) {
const assertionFunction = Reflect.get(target, key, receiver)
Expand Down Expand Up @@ -75,6 +76,7 @@ export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] {
}, timeout)
const check = async () => {
try {
chai.util.flag(assertion, '_name', key)
const obj = await fn()
chai.util.flag(assertion, 'object', obj)
resolve(await assertionFunction.call(assertion, ...args))
Expand Down
25 changes: 17 additions & 8 deletions packages/vitest/src/node/reporters/reported-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type {
TaskMeta,
} from '@vitest/runner'
import type { TestError } from '@vitest/utils'
import { getTestName } from '../../utils/tasks'
import type { WorkspaceProject } from '../workspace'
import { TestProject } from '../reported-workspace-project'

Expand Down Expand Up @@ -101,7 +100,12 @@ export class TestCase extends ReportedTaskImplementation {
*/
public get fullName(): string {
if (this.#fullName === undefined) {
this.#fullName = getTestName(this.task, ' > ')
if (this.parent.type !== 'module') {
this.#fullName = `${this.parent.fullName} > ${this.name}`
}
else {
this.#fullName = this.name
}
}
return this.#fullName
}
Expand Down Expand Up @@ -198,7 +202,7 @@ class TestCollection {
/**
* Filters all tests that are part of this collection and its children.
*/
*allTests(state?: TestResult['state'] | 'running'): IterableIterator<TestCase> {
*allTests(state?: TestResult['state'] | 'running'): Generator<TestCase, undefined, void> {
for (const child of this) {
if (child.type === 'suite') {
yield * child.children.allTests(state)
Expand All @@ -218,7 +222,7 @@ class TestCollection {
/**
* Filters only the tests that are part of this collection.
*/
*tests(state?: TestResult['state'] | 'running'): IterableIterator<TestCase> {
*tests(state?: TestResult['state'] | 'running'): Generator<TestCase, undefined, void> {
for (const child of this) {
if (child.type !== 'test') {
continue
Expand All @@ -239,7 +243,7 @@ class TestCollection {
/**
* Filters only the suites that are part of this collection.
*/
*suites(): IterableIterator<TestSuite> {
*suites(): Generator<TestSuite, undefined, void> {
for (const child of this) {
if (child.type === 'suite') {
yield child
Expand All @@ -250,7 +254,7 @@ class TestCollection {
/**
* Filters all suites that are part of this collection and its children.
*/
*allSuites(): IterableIterator<TestSuite> {
*allSuites(): Generator<TestSuite, undefined, void> {
for (const child of this) {
if (child.type === 'suite') {
yield child
Expand All @@ -259,7 +263,7 @@ class TestCollection {
}
}

*[Symbol.iterator](): IterableIterator<TestSuite | TestCase> {
*[Symbol.iterator](): Generator<TestSuite | TestCase, undefined, void> {
for (const task of this.#task.tasks) {
yield getReportedTask(this.#project, task) as TestSuite | TestCase
}
Expand Down Expand Up @@ -328,7 +332,12 @@ export class TestSuite extends SuiteImplementation {
*/
public get fullName(): string {
if (this.#fullName === undefined) {
this.#fullName = getTestName(this.task, ' > ')
if (this.parent.type !== 'module') {
this.#fullName = `${this.parent.fullName} > ${this.name}`
}
else {
this.#fullName = this.name
}
}
return this.#fullName
}
Expand Down
Loading

0 comments on commit 2c6702d

Please sign in to comment.