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

Use Vite include/exclude config #187

Merged
merged 1 commit into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as vscode from 'vscode'
import semver from 'semver'
import type { WorkspaceConfiguration, WorkspaceFolder } from 'vscode'
import type { ResolvedConfig } from 'vitest'
import { configDefaults } from 'vitest/config'
import { isDefinitelyVitestEnv, mayBeVitestEnv } from './pure/isVitestEnv'
import { getVitestCommand, getVitestVersion, isNodeAvailable } from './pure/utils'
import { log } from './log'
Expand All @@ -10,8 +12,8 @@ export function getConfigValue<T>(
rootConfig: WorkspaceConfiguration,
folderConfig: WorkspaceConfiguration,
key: string,
defaultValue: T,
): T {
defaultValue?: T,
): T | undefined {
return folderConfig.get(key) ?? rootConfig.get(key) ?? defaultValue
}

Expand All @@ -25,18 +27,26 @@ export function getConfig(workspaceFolder?: WorkspaceFolder | vscode.Uri | strin
const folderConfig = vscode.workspace.getConfiguration('vitest', workspace)
const rootConfig = vscode.workspace.getConfiguration('vitest')

const get = <T>(key: string, defaultValue: T) => getConfigValue<T>(rootConfig, folderConfig, key, defaultValue)
const get = <T>(key: string, defaultValue?: T) => getConfigValue<T>(rootConfig, folderConfig, key, defaultValue)

return {
env: get<null | Record<string, string>>('nodeEnv', null),
commandLine: get<string | undefined>('commandLine', undefined),
include: get<string[]>('include', []),
exclude: get<string[]>('exclude', []),
include: get<string[]>('include'),
exclude: get<string[]>('exclude'),
enable: get<boolean>('enable', false),
debugExclude: get<string[]>('debugExclude', []),
}
}

export function getCombinedConfig(config: ResolvedConfig, workspaceFolder?: WorkspaceFolder | vscode.Uri | string) {
const vitestConfig = getConfig(workspaceFolder)
return {
exclude: vitestConfig.exclude || config.exclude || configDefaults.exclude,
include: vitestConfig.include || config.include || configDefaults.include,
}
}

export function getRootConfig() {
const rootConfig = vscode.workspace.getConfiguration('vitest')

Expand Down
17 changes: 10 additions & 7 deletions src/discover.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path, { sep } from 'path'
import * as vscode from 'vscode'
import minimatch from 'minimatch'
import type { ResolvedConfig } from 'vitest'
import parse from './pure/parsers'
import type { NamedBlock } from './pure/parsers/parser_nodes'
import type { TestData } from './TestData'
Expand All @@ -13,7 +14,7 @@ import {
} from './TestData'
import { shouldIncludeFile } from './vscodeUtils'

import { getConfig, vitestEnvironmentFolders } from './config'
import { getCombinedConfig, vitestEnvironmentFolders } from './config'
import { log } from './log'

export class TestFileDiscoverer extends vscode.Disposable {
Expand All @@ -22,8 +23,9 @@ export class TestFileDiscoverer extends vscode.Disposable {
private workspaceCommonPrefix: Map<string, string> = new Map()
private workspaceItems: Map<string, Set<vscode.TestItem>> = new Map()
private pathToFileItem: Map<string, TestFile> = new Map()
private config: ResolvedConfig

constructor() {
constructor(config: ResolvedConfig) {
super(() => {
for (const watch of this.lastWatches)
watch.dispose()
Expand All @@ -33,6 +35,7 @@ export class TestFileDiscoverer extends vscode.Disposable {
this.pathToFileItem.clear()
this.workspaceCommonPrefix.clear()
})
this.config = config
this.workspacePaths
= vscode.workspace.workspaceFolders?.map(x => x.uri.fsPath) || []
}
Expand All @@ -49,8 +52,8 @@ export class TestFileDiscoverer extends vscode.Disposable {
const watchers = [] as vscode.FileSystemWatcher[]
await Promise.all(
vitestEnvironmentFolders.map(async (workspaceFolder) => {
const exclude = getConfig(workspaceFolder).exclude
for (const include of getConfig(workspaceFolder).include) {
const exclude = getCombinedConfig(this.config, workspaceFolder).exclude
for (const include of getCombinedConfig(this.config, workspaceFolder).include) {
const pattern = new vscode.RelativePattern(
workspaceFolder.uri,
include,
Expand Down Expand Up @@ -103,8 +106,8 @@ export class TestFileDiscoverer extends vscode.Disposable {

await Promise.all(
vscode.workspace.workspaceFolders.map(async (workspaceFolder) => {
const exclude = getConfig(workspaceFolder).exclude
for (const include of getConfig(workspaceFolder).include) {
const exclude = getCombinedConfig(this.config, workspaceFolder).exclude
for (const include of getCombinedConfig(this.config, workspaceFolder).include) {
const pattern = new vscode.RelativePattern(
workspaceFolder.uri,
include,
Expand All @@ -130,7 +133,7 @@ export class TestFileDiscoverer extends vscode.Disposable {
if (e.uri.scheme !== 'file')
return

if (!shouldIncludeFile(e.uri.fsPath))
if (!shouldIncludeFile(e.uri.fsPath, this.config))
return

const { file, data } = this.getOrCreateFile(ctrl, e.uri)
Expand Down
13 changes: 10 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as vscode from 'vscode'

import { effect } from '@vue/reactivity'

import type { ResolvedConfig } from 'vitest'
import { Command } from './command'
import {
detectVitestEnvironmentFolders, extensionId, getVitestWorkspaceConfigs,
Expand All @@ -17,6 +18,7 @@ import { TestFile, WEAKMAP_TEST_DATA } from './TestData'
import { TestWatcher } from './watch'

import type { VitestWorkspaceConfig } from './config'
import { fetchVitestConfig } from './pure/watch/vitestConfig'

export async function activate(context: vscode.ExtensionContext) {
await detectVitestEnvironmentFolders()
Expand All @@ -26,7 +28,6 @@ export async function activate(context: vscode.ExtensionContext) {
}

const ctrl = vscode.tests.createTestController(`${extensionId}`, 'Vitest')
const fileDiscoverer = registerDiscovery(ctrl, context)

const workspaceConfigs = await getVitestWorkspaceConfigs()
// enable run/debug/watch tests only if vitest version >= 0.12.0
Expand All @@ -50,6 +51,12 @@ export async function activate(context: vscode.ExtensionContext) {
return
}

const config = await fetchVitestConfig(workspaceConfigs)
if (!config) {
vscode.window.showWarningMessage('Cannot run tests: no Vitest config found.')
return
}
const fileDiscoverer = registerDiscovery(ctrl, context, config)
registerRunDebugWatchHandler(ctrl, workspaceConfigs, fileDiscoverer, context)
context.subscriptions.push(
ctrl,
Expand Down Expand Up @@ -83,8 +90,8 @@ function workspacesCompatibilityCheck(workspaceConfigs: VitestWorkspaceConfig[])
return true
}

function registerDiscovery(ctrl: vscode.TestController, context: vscode.ExtensionContext) {
const fileDiscoverer = new TestFileDiscoverer()
function registerDiscovery(ctrl: vscode.TestController, context: vscode.ExtensionContext, config: ResolvedConfig) {
const fileDiscoverer = new TestFileDiscoverer(config)
// run on refreshing test list
ctrl.refreshHandler = async () => {
await fileDiscoverer.discoverAllTestFilesInWorkspace(ctrl)
Expand Down
77 changes: 77 additions & 0 deletions src/pure/watch/vitestConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { effect, reactive } from '@vue/reactivity'
import type { ResolvedConfig } from 'vitest'
import getPort from 'get-port'
import { log } from '../../log'
import type { VitestWorkspaceConfig } from '../../config'
import { getConfig } from '../../config'
import { execWithLog, sanitizeFilePath } from '../utils'
import { createClient } from './ws-client'

async function connectAndFetchConfig(
{ port, url = `ws://localhost:${port}/__vitest_api__`, reconnectInterval, reconnectTries }: {
url?: string
port: number
reconnectInterval?: number
reconnectTries?: number
},
) {
let onFailedConnection: (() => void) | undefined
const client = createClient(url, {
reactive: reactive as any,
reconnectInterval,
reconnectTries,
onFailedConnection: () => onFailedConnection?.(),
})

return new Promise<ResolvedConfig>((resolve, reject) => {
onFailedConnection = () => reject(new Error ('Unable to connect to Vitest API'))
const handled = new WeakSet()
effect(() => {
const ws = client.ws
if (!handled.has(ws)) {
handled.add(ws)
ws.addEventListener('open', () => {
log.info('WS Opened')
client.rpc.getConfig().then((_config) => {
client.dispose()
resolve(_config)
})
})

ws.addEventListener('error', (e) => {
console.error('WS ERROR', e)
})

ws.addEventListener('close', () => {
log.info('WS Close')
})
}
})
})
}

export async function fetchVitestConfig(
workspaceConfigs: VitestWorkspaceConfig[],
) {
const port = await getPort()
const workspace = workspaceConfigs.find(workspace =>
workspace.isCompatible && !workspace.isDisabled && workspace.isUsingVitestForSure)
if (!workspace)
return
const folder = workspace.workspace.uri.fsPath
const childProcess = execWithLog(
workspace.cmd,
[...workspace.args, '--api.port', port.toString(), '--api.host', '127.0.0.1'],
{
cwd: sanitizeFilePath(folder),
env: { ...process.env, ...getConfig(folder).env },
},
).child
const config = await connectAndFetchConfig({
port,
reconnectInterval: 500,
reconnectTries: 20,
})
childProcess.kill()
return config
}
4 changes: 4 additions & 0 deletions src/pure/watch/ws-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface VitestClientOptions {
reactive?: <T>(v: T) => T
ref?: <T>(v: T) => { value: T }
WebSocketConstructor?: typeof WebSocket
onFailedConnection?: () => void
}

export interface VitestClient {
Expand All @@ -112,6 +113,7 @@ export function createClient(url: string, options: VitestClientOptions = {}) {
reconnectTries = 10,
reactive = v => v,
WebSocketConstructor = globalThis.WebSocket,
onFailedConnection,
} = options

let tries = reconnectTries
Expand Down Expand Up @@ -190,6 +192,8 @@ export function createClient(url: string, options: VitestClientOptions = {}) {
tries -= 1
if (!opened && autoReconnect && tries > 0)
setTimeout(reconnect, reconnectInterval)
else if (autoReconnect && tries === 0)
onFailedConnection?.()
})
}

Expand Down
7 changes: 4 additions & 3 deletions src/vscodeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { TextDecoder } from 'util'
import type { Uri } from 'vscode'
import { workspace } from 'vscode'
import minimatch from 'minimatch'
import { getConfig } from './config'
import type { ResolvedConfig } from 'vitest'
import { getCombinedConfig } from './config'

const textDecoder = new TextDecoder('utf-8')

Expand All @@ -17,8 +18,8 @@ export const getContentFromFilesystem = async (uri: Uri) => {
}
}

export function shouldIncludeFile(path: string) {
const { include, exclude } = getConfig()
export function shouldIncludeFile(path: string, config: ResolvedConfig) {
const { include, exclude } = getCombinedConfig(config)
return (
include.some(x => minimatch(path, x))
&& exclude.every(x => !minimatch(path, x, { dot: true }))
Expand Down