Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add workspace support #3103

Merged
merged 36 commits into from Apr 9, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0bd9758
feat: add workspace support
sheremet-va Mar 29, 2023
504c631
chore: enable preTransform
sheremet-va Mar 29, 2023
5fae5a0
chore: cleanup
sheremet-va Mar 29, 2023
756234c
chore: fix coverage, cleanup
sheremet-va Mar 29, 2023
90f9b3a
chore: cleanup
sheremet-va Mar 30, 2023
7b8ae2a
chore: add workspace types
sheremet-va Mar 30, 2023
2988d5c
fix: resolve browser relative to workspace
sheremet-va Mar 30, 2023
ab328a6
chore: use dirname for fallback name
sheremet-va Mar 30, 2023
b4cb35f
chore: allow changing browser provider per workspace
sheremet-va Apr 5, 2023
9a522d3
chore: fix types
sheremet-va Apr 5, 2023
b3e7cf5
chore: cleanup
sheremet-va Apr 8, 2023
e0a2813
test: fix -t in workspaces
sheremet-va Apr 8, 2023
06b7519
chore: fix browser running in a workspace
sheremet-va Apr 8, 2023
b2cfcff
refactor: move workspaces into a separate file
sheremet-va Apr 8, 2023
f85088c
chore: make workspaces public
sheremet-va Apr 8, 2023
bde49db
chore: always show workspace name
sheremet-va Apr 8, 2023
9a35fd0
test: editing file in a workspace reruns tests
sheremet-va Apr 8, 2023
dc2365c
chore: fix workspaces test
sheremet-va Apr 8, 2023
b30a416
chore: remove not used file
sheremet-va Apr 8, 2023
baab9dc
fix: allow inline workspaces
sheremet-va Apr 8, 2023
f6ab824
test: more workspaces tests
sheremet-va Apr 8, 2023
57eb347
chore: consider project name ony if specified
sheremet-va Apr 8, 2023
a959c3e
chore: cleanup
sheremet-va Apr 8, 2023
14642d6
chore: make sequencer understand workspaces
sheremet-va Apr 8, 2023
eb5a2fe
test: fix sequencer tests
sheremet-va Apr 8, 2023
04ca1c6
test: fix reporters test
sheremet-va Apr 8, 2023
b2df74f
chore: add support for json workspaces and "extends" field in workspa…
sheremet-va Apr 9, 2023
d5c9ce7
chore: add inspector to external modules
sheremet-va Apr 9, 2023
145bc8d
docs: add workspaces to feature list
sheremet-va Apr 9, 2023
2260ce7
chore: fix how tests are running in no-threads mode, respect CLI argu…
sheremet-va Apr 9, 2023
d1fc04c
refactor: rename workspace->project
sheremet-va Apr 9, 2023
964d5fb
chore: fix type errors
sheremet-va Apr 9, 2023
9ac9636
docs: more docs
sheremet-va Apr 9, 2023
b912e73
chore: add * tip to config docs
sheremet-va Apr 9, 2023
d40eb00
chore: fix docs link
sheremet-va Apr 9, 2023
f2b8c78
chore: rename workspace
sheremet-va Apr 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -41,11 +41,12 @@ A blazing fast unit test framework powered by Vite.
- Components testing ([Vue](./examples/vue), [React](./examples/react), [Svelte](./examples/svelte), [Lit](./examples/lit), [Vitesse](./examples/vitesse))
- Workers multi-threading via [Tinypool](https://github.com/tinylibs/tinypool) (a lightweight fork of [Piscina](https://github.com/piscinajs/piscina))
- Benchmarking support with [Tinybench](https://github.com/tinylibs/tinybench)
- Workspaces support
- ESM first, top level await
- Out-of-box TypeScript / JSX support
- Filtering, timeouts, concurrent for suite and tests

> Vitest requires Vite >=v3.0.0 and Node >=v14
> Vitest requires Vite >=v3.0.0 and Node >=v14.18


```ts
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/components/FeaturesList.vue
Expand Up @@ -13,6 +13,7 @@
<ListItem>Workers multi-threading via <a target="_blank" href="https://github.com/tinylibs/tinypool" rel="noopener noreferrer">Tinypool</a></ListItem>
<ListItem>Benchmarking support with <a target="_blank" href="https://github.com/tinylibs/tinybench" rel="noopener noreferrer">Tinybench</a></ListItem>
<ListItem>Filtering, timeouts, concurrent for suite and tests</ListItem>
<ListItem>Workspaces support</ListItem>
<ListItem>
<a href="/guide/snapshot">
Jest-compatible Snapshot
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/index.md
Expand Up @@ -32,7 +32,7 @@ pnpm add -D vitest
```

:::tip
Vitest requires Vite >=v3.0.0 and Node >=v14
Vitest requires Vite >=v3.0.0 and Node >=v14.18
:::

It is recommended that you install a copy of `vitest` in your `package.json`, using one of the methods listed above. However, if you would prefer to run `vitest` directly, you can use `npx vitest` (the `npx` command comes with npm and Node.js).
Expand Down
4 changes: 4 additions & 0 deletions packages/browser/src/client/main.ts
Expand Up @@ -66,6 +66,10 @@ ws.addEventListener('open', async () => {
moduleCache: new Map(),
rpc: client.rpc,
safeRpc,
durations: {
environment: 0,
prepare: 0,
},
}

const paths = getQueryPaths()
Expand Down
1 change: 1 addition & 0 deletions packages/coverage-c8/rollup.config.js
Expand Up @@ -17,6 +17,7 @@ const external = [
...builtinModules,
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
'node:inspector',
'c8/lib/report.js',
'c8/lib/commands/check-coverage.js',
'vitest',
Expand Down
2 changes: 1 addition & 1 deletion packages/runner/src/collect.ts
Expand Up @@ -18,7 +18,7 @@ export async function collectTests(paths: string[], runner: VitestRunner): Promi
for (const filepath of paths) {
const path = relative(config.root, filepath)
const file: File = {
id: generateHash(path),
id: generateHash(`${path}${config.name || ''}`),
name: path,
type: 'suite',
mode: 'run',
Expand Down
5 changes: 4 additions & 1 deletion packages/vitest/src/api/setup.ts
Expand Up @@ -10,10 +10,13 @@ import { API_PATH } from '../constants'
import type { Vitest } from '../node'
import type { File, ModuleGraphData, Reporter, TaskResultPack, UserConsoleLog } from '../types'
import { getModuleGraph, isPrimitive } from '../utils'
import type { VitestWorkspace } from '../node/workspace'
import { parseErrorStacktrace } from '../utils/source-map'
import type { TransformResultWithSource, WebSocketEvents, WebSocketHandlers } from './types'

export function setup(ctx: Vitest, server?: ViteDevServer) {
export function setup(vitestOrWorkspace: Vitest | VitestWorkspace, server?: ViteDevServer) {
const ctx = 'ctx' in vitestOrWorkspace ? vitestOrWorkspace.ctx : vitestOrWorkspace

const wss = new WebSocketServer({ noServer: true })

const clients = new Map<WebSocket, BirpcReturn<WebSocketEvents>>()
Expand Down
17 changes: 17 additions & 0 deletions packages/vitest/src/config.ts
@@ -1,9 +1,15 @@
import type { ConfigEnv, UserConfig as ViteUserConfig } from 'vite'
import type { WorkspaceConfig } from './types'

export interface UserConfig extends ViteUserConfig {
test?: ViteUserConfig['test']
}

export interface UserWorkspaceConfig extends ViteUserConfig {
extends?: string
test?: WorkspaceConfig
}

// will import vitest declare test in module 'vite'
export { configDefaults, defaultInclude, defaultExclude, coverageConfigDefaults } from './defaults'
export { mergeConfig } from 'vite'
Expand All @@ -12,6 +18,17 @@ export type { ConfigEnv }
export type UserConfigFn = (env: ConfigEnv) => UserConfig | Promise<UserConfig>
export type UserConfigExport = UserConfig | Promise<UserConfig> | UserConfigFn

export type UserWorkspaceConfigFn = (env: ConfigEnv) => UserWorkspaceConfig | Promise<UserWorkspaceConfig>
export type UserWorkspaceConfigExport = UserWorkspaceConfig | Promise<UserWorkspaceConfig> | UserWorkspaceConfigFn

export function defineConfig(config: UserConfigExport) {
return config
}

export function defineWorkspace(config: UserWorkspaceConfigExport) {
return config
}

export function defineWorkspaces(config: (string | UserWorkspaceConfigExport)[]) {
return config
}
44 changes: 31 additions & 13 deletions packages/vitest/src/constants.ts
@@ -1,25 +1,43 @@
// if changed, update also jsdocs and docs
export const defaultPort = 51204
export const defaultBrowserPort = 63315

export const EXIT_CODE_RESTART = 43

export const API_PATH = '/__vitest_api__'

export const configFiles = [
'vitest.config.ts',
'vitest.config.mts',
'vitest.config.cts',
'vitest.config.js',
'vitest.config.mjs',
'vitest.config.cjs',
'vite.config.ts',
'vite.config.mts',
'vite.config.cts',
'vite.config.js',
'vite.config.mjs',
'vite.config.cjs',
export const CONFIG_NAMES = [
'vitest.config',
'vite.config',
]

const WORKSPACES_NAMES = [
'vitest.workspaces',
'vitest.projects',
]

const CONFIG_EXTENSIONS = [
'.ts',
'.mts',
'.cts',
'.js',
'.mjs',
'.cjs',
]

export const configFiles = CONFIG_NAMES.flatMap(name =>
CONFIG_EXTENSIONS.map(ext => name + ext),
)

const WORKSPACES_EXTENSIONS = [
...CONFIG_EXTENSIONS,
'.json',
]

export const workspacesFiles = WORKSPACES_NAMES.flatMap(name =>
WORKSPACES_EXTENSIONS.map(ext => name + ext),
)

export const globalApis = [
// suite
'suite',
Expand Down
27 changes: 8 additions & 19 deletions packages/vitest/src/integrations/browser/server.ts
@@ -1,15 +1,15 @@
import { createServer } from 'vite'
import { resolve } from 'pathe'
import { findUp } from 'find-up'
import { configFiles } from '../../constants'
import type { Vitest } from '../../node'
import { configFiles, defaultBrowserPort } from '../../constants'
import type { UserConfig } from '../../types/config'
import { ensurePackageInstalled } from '../../node/pkg'
import { resolveApiServerConfig } from '../../node/config'
import { CoverageTransform } from '../../node/plugins/coverageTransform'
import type { VitestWorkspace } from '../../node/workspace'

export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
const root = ctx.config.root
export async function createBrowserServer(workspace: VitestWorkspace, options: UserConfig) {
const root = workspace.config.root

await ensurePackageInstalled('@vitest/browser', root)

Expand All @@ -21,7 +21,7 @@ export async function createBrowserServer(ctx: Vitest, options: UserConfig) {

const server = await createServer({
logLevel: 'error',
mode: ctx.config.mode,
mode: workspace.config.mode,
configFile: configPath,
// watch is handled by Vitest
server: {
Expand All @@ -32,29 +32,18 @@ export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
},
plugins: [
(await import('@vitest/browser')).default('/'),
CoverageTransform(ctx),
CoverageTransform(workspace.ctx),
{
enforce: 'post',
name: 'vitest:browser:config',
async config(config) {
const server = resolveApiServerConfig(config.test?.browser || {}) || {
port: 63315,
port: defaultBrowserPort,
}

config.server = server
config.server.fs = { strict: false }

config.optimizeDeps ??= {}
config.optimizeDeps.entries ??= []

const [...entries] = await ctx.globAllTestFiles(ctx.config, ctx.config.dir || root)
entries.push(...ctx.config.setupFiles)

if (typeof config.optimizeDeps.entries === 'string')
config.optimizeDeps.entries = [config.optimizeDeps.entries]

config.optimizeDeps.entries.push(...entries)

return {
resolve: {
alias: config.test?.alias,
Expand All @@ -68,7 +57,7 @@ export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
await server.listen()
await server.watcher.close()

;(await import('../../api/setup')).setup(ctx, server)
;(await import('../../api/setup')).setup(workspace, server)

return server
}
6 changes: 3 additions & 3 deletions packages/vitest/src/node/browser/playwright.ts
@@ -1,7 +1,7 @@
import type { Page } from 'playwright'
import type { BrowserProvider, BrowserProviderOptions } from '../../types/browser'
import { ensurePackageInstalled } from '../pkg'
import type { Vitest } from '../core'
import type { VitestWorkspace } from '../workspace'

export const playwrightBrowsers = ['firefox', 'webkit', 'chromium'] as const
export type PlaywrightBrowser = typeof playwrightBrowsers[number]
Expand All @@ -15,13 +15,13 @@ export class PlaywrightBrowserProvider implements BrowserProvider {

private cachedBrowser: Page | null = null
private browser!: PlaywrightBrowser
private ctx!: Vitest
private ctx!: VitestWorkspace

getSupportedBrowsers() {
return playwrightBrowsers
}

async initialize(ctx: Vitest, { browser }: PlaywrightProviderOptions) {
async initialize(ctx: VitestWorkspace, { browser }: PlaywrightProviderOptions) {
this.ctx = ctx
this.browser = browser

Expand Down
6 changes: 3 additions & 3 deletions packages/vitest/src/node/browser/webdriver.ts
@@ -1,7 +1,7 @@
import type { Browser } from 'webdriverio'
import type { BrowserProvider, BrowserProviderOptions } from '../../types/browser'
import { ensurePackageInstalled } from '../pkg'
import type { Vitest } from '../core'
import type { VitestWorkspace } from '../workspace'

export const webdriverBrowsers = ['firefox', 'chrome', 'edge', 'safari'] as const
export type WebdriverBrowser = typeof webdriverBrowsers[number]
Expand All @@ -16,13 +16,13 @@ export class WebdriverBrowserProvider implements BrowserProvider {
private cachedBrowser: Browser | null = null
private stopSafari: () => void = () => {}
private browser!: WebdriverBrowser
private ctx!: Vitest
private ctx!: VitestWorkspace

getSupportedBrowsers() {
return webdriverBrowsers
}

async initialize(ctx: Vitest, { browser }: WebdriverProviderOptions) {
async initialize(ctx: VitestWorkspace, { browser }: WebdriverProviderOptions) {
this.ctx = ctx
this.browser = browser

Expand Down
23 changes: 18 additions & 5 deletions packages/vitest/src/node/cache/files.ts
@@ -1,23 +1,36 @@
import fs from 'node:fs'
import type { Stats } from 'node:fs'
import { relative } from 'pathe'
import type { WorkspaceSpec } from '../pool'

type FileStatsCache = Pick<Stats, 'size'>

export class FilesStatsCache {
public cache = new Map<string, FileStatsCache>()

public getStats(fsPath: string): FileStatsCache | undefined {
return this.cache.get(fsPath)
public getStats(key: string): FileStatsCache | undefined {
return this.cache.get(key)
}

public async updateStats(fsPath: string) {
public async populateStats(root: string, specs: WorkspaceSpec[]) {
const promises = specs.map((spec) => {
const key = `${spec[0].getName()}:${relative(root, spec[1])}`
return this.updateStats(spec[1], key)
})
await Promise.all(promises)
}

public async updateStats(fsPath: string, key: string) {
if (!fs.existsSync(fsPath))
return
const stats = await fs.promises.stat(fsPath)
this.cache.set(fsPath, { size: stats.size })
this.cache.set(key, { size: stats.size })
}

public removeStats(fsPath: string) {
this.cache.delete(fsPath)
this.cache.forEach((_, key) => {
if (key.endsWith(fsPath))
this.cache.delete(key)
})
}
}
8 changes: 4 additions & 4 deletions packages/vitest/src/node/cache/index.ts
Expand Up @@ -12,12 +12,12 @@ export class VitestCache {
results = new ResultsCache()
stats = new FilesStatsCache()

getFileTestResults(id: string) {
return this.results.getResults(id)
getFileTestResults(key: string) {
return this.results.getResults(key)
}

getFileStats(id: string) {
return this.stats.getStats(id)
getFileStats(key: string) {
return this.stats.getStats(key)
}

static resolveCacheDir(root: string, dir: string | undefined) {
Expand Down