Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- **Run**, **debug**, and **watch** Vitest tests in Visual Studio Code.
- **Coverage** support (requires VS Code >= 1.88)
- An `@open` tag can be used when filtering tests, to only show the tests open in the editor.
- **Inline console.log display**: Console logs appear inline in the editor next to the code that produced them

## Requirements

Expand Down
23 changes: 17 additions & 6 deletions packages/extension/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,18 +153,29 @@ export class TestRunner extends vscode.Disposable {
this.endTestRun()
})

api.onConsoleLog(({ content, taskId }) => {
const testItem = taskId ? tree.getTestItemByTaskId(taskId) : undefined
api.onConsoleLog((consoleLog) => {
const testItem = consoleLog.taskId ? tree.getTestItemByTaskId(consoleLog.taskId) : undefined
const testRun = this.testRun
if (testRun) {
// Create location from parsed console log for inline display
let location: vscode.Location | undefined
if (consoleLog.parsedLocation) {
const uri = vscode.Uri.file(consoleLog.parsedLocation.file)
const position = new vscode.Position(
consoleLog.parsedLocation.line,
consoleLog.parsedLocation.column,
)
location = new vscode.Location(uri, position)
}

testRun.appendOutput(
formatTestOutput(content),
undefined,
formatTestOutput(consoleLog.content) + (consoleLog.browser ? '\r\n' : ''),
location,
testItem,
)
}
else {
log.info('[TEST]', content)
log.info('[TEST]', consoleLog.content)
}
})
}
Expand Down Expand Up @@ -765,7 +776,7 @@ function formatTestPattern(tests: readonly vscode.TestItem[]) {
}

function formatTestOutput(output: string) {
return output.replace(/(?<!\r)\n/g, '\r\n')
return stripVTControlCharacters(output.replace(/(?<!\r)\n/g, '\r\n'))
}

function labelTestItems(items: readonly vscode.TestItem[] | undefined) {
Expand Down
11 changes: 10 additions & 1 deletion packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ export type ExtensionTestFileSpecification = [
ExtensionTestFileMetadata,
]

export interface ExtensionUserConsoleLog extends UserConsoleLog {
// Parsed location from stack trace for inline display
parsedLocation?: {
file: string
line: number // 0-based line number
column: number
}
}

export interface ExtensionWorkerTransport {
getFiles: () => Promise<ExtensionTestFileSpecification[]>
collectTests: (testFile: ExtensionTestSpecification[]) => Promise<void>
Expand All @@ -54,7 +63,7 @@ export interface ExtensionWorkerTransport {
}

export interface ExtensionWorkerEvents {
onConsoleLog: (log: UserConsoleLog) => void
onConsoleLog: (log: ExtensionUserConsoleLog) => void
onTaskUpdate: (task: RunnerTaskResultPack[]) => void
onTestRunEnd: (files: RunnerTestFile[], unhandledError: string, collecting?: boolean) => void
onCollected: (file: RunnerTestFile, collecting?: boolean) => void
Expand Down
3 changes: 3 additions & 0 deletions packages/worker-legacy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ export async function initVitest(
}
},
configureVitest(context) {
// Enable printConsoleTrace for inline console log display
context.project.config.printConsoleTrace = true

const options = context.project.config.browser
if (options?.enabled && typeof data.debug === 'object') {
context.project.config.setupFiles.push(meta.setupFilePaths.browserDebug)
Expand Down
54 changes: 43 additions & 11 deletions packages/worker-legacy/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,47 @@ export class VSCodeReporter implements Reporter {
}

onUserConsoleLog(log: UserConsoleLog) {
this.rpc.onConsoleLog(log)
// Parse stack trace to extract file location for inline display
const extendedLog = log as any
if (log.origin) {
try {
const stacks = this.parseStackTrace({ stack: log.origin } as any, log.taskId)
if (stacks && stacks.length > 0) {
const firstStack = stacks[0]
if (firstStack.file && firstStack.line != null && firstStack.column != null) {
extendedLog.parsedLocation = {
file: firstStack.file,
line: firstStack.line - 1, // Convert to 0-based
column: firstStack.column,
}
}
}
}
catch {
// If parsing fails, continue without parsed location
}
}
this.rpc.onConsoleLog(extendedLog)
}

parseStackTrace(obj: unknown, taskId: string | undefined) {
const project = taskId
? this.vitest.getProjectByTaskId(taskId)
: this.vitest.getCoreWorkspaceProject()

// the new version uses browser.parseErrorStacktrace
if ('getBrowserSourceMapModuleById' in project) {
return parseErrorStacktrace(obj as Error, {
getSourceMap: file => (project as any).getBrowserSourceMapModuleById(file),
})
}

const task = taskId && this.vitest.state.idMap.get(taskId)
const isBrowser = task && task.file?.pool === 'browser'

return isBrowser
? project.browser?.parseErrorStacktrace(obj)
: parseErrorStacktrace(obj as Error)
}

onTaskUpdate(packs: TaskResultPack[]) {
Expand All @@ -111,26 +151,18 @@ export class VSCodeReporter implements Reporter {
if ('getBrowserSourceMapModuleById' in project) {
result?.errors?.forEach((error) => {
if (typeof error === 'object' && error) {
error.stacks = parseErrorStacktrace(error, {
getSourceMap: file => (project as any).getBrowserSourceMapModuleById(file),
})
error.stacks = this.parseStackTrace(error, taskId)
}
})
return
}

const task = this.vitest.state.idMap.get(taskId)
const isBrowser = task && task.file?.pool === 'browser'

result?.errors?.forEach((error) => {
if (isPrimitive(error)) {
return
}

const stacks = isBrowser
? project.browser?.parseErrorStacktrace(error)
: parseErrorStacktrace(error)
error.stacks = stacks
error.stacks = this.parseStackTrace(error, taskId)
})
})

Expand Down
1 change: 1 addition & 0 deletions packages/worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export async function initVitest(
]
return {
test: {
printConsoleTrace: true,
coverage: {
reportOnFailure: true,
reportsDirectory: join(tmpdir(), `vitest-coverage-${randomUUID()}`),
Expand Down
30 changes: 29 additions & 1 deletion packages/worker/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Vite,
Vitest as VitestCore,
} from 'vitest/node'
import { parseErrorStacktrace } from '@vitest/utils/source-map'
import { ExtensionWorker } from './worker'

interface VSCodeReporterOptions {
Expand Down Expand Up @@ -59,7 +60,34 @@ export class VSCodeReporter implements Reporter {
}

onUserConsoleLog(log: UserConsoleLog) {
this.rpc.onConsoleLog(log)
// Parse stack trace to extract file location for inline display
const extendedLog = log as any
if (log.origin) {
try {
const task = log.taskId ? this.vitest.state.idMap.get(log.taskId) : null
const project = task
? this.vitest.state.getReportedEntity(task)!.project
: this.vitest.getRootProject()
const stacks = log.browser
? project.browser?.parseErrorStacktrace({ stack: log.origin } as any)
: parseErrorStacktrace({ stack: log.origin } as any)

if (stacks && stacks.length > 0) {
const firstStack = stacks[0]
if (firstStack.file && firstStack.line != null && firstStack.column != null) {
extendedLog.parsedLocation = {
file: firstStack.file,
line: firstStack.line - 1, // Convert to 0-based
column: firstStack.column,
}
}
}
}
catch {
// If parsing fails, continue without parsed location
}
}
this.rpc.onConsoleLog(extendedLog)
}

onTaskUpdate(packs: RunnerTaskResultPack[]) {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"root":["./debug-shims.d.ts","./packages/extension/src/api.ts","./packages/extension/src/config.ts","./packages/extension/src/constants.ts","./packages/extension/src/coverage.ts","./packages/extension/src/debug.ts","./packages/extension/src/diagnostic.ts","./packages/extension/src/extension.ts","./packages/extension/src/log.ts","./packages/extension/src/polyfills.ts","./packages/extension/src/runner.ts","./packages/extension/src/tagsmanager.ts","./packages/extension/src/testtree.ts","./packages/extension/src/testtreedata.ts","./packages/extension/src/utils.ts","./packages/extension/src/watcher.ts","./packages/extension/src/api/child_process.ts","./packages/extension/src/api/pkg.ts","./packages/extension/src/api/resolve.ts","./packages/extension/src/api/rpc.ts","./packages/extension/src/api/terminal.ts","./packages/extension/src/api/types.ts","./packages/extension/src/api/ws.ts","./packages/extension/src/worker/index.ts","./packages/extension/src/worker/setupfile.ts","./packages/shared/src/emitter.ts","./packages/shared/src/index.ts","./packages/shared/src/rpc.ts","./packages/shared/src/utils.ts","./packages/worker/src/coverage.ts","./packages/worker/src/index.ts","./packages/worker/src/reporter.ts","./packages/worker/src/runner.ts","./packages/worker/src/watcher.ts","./packages/worker/src/worker.ts","./packages/worker-legacy/src/collect.ts","./packages/worker-legacy/src/coverage.ts","./packages/worker-legacy/src/index.ts","./packages/worker-legacy/src/reporter.ts","./packages/worker-legacy/src/types.ts","./packages/worker-legacy/src/watcher.ts","./packages/worker-legacy/src/worker.ts"],"version":"5.8.3"}
{"root":["./debug-shims.d.ts","./packages/extension/src/api.ts","./packages/extension/src/config.ts","./packages/extension/src/constants.ts","./packages/extension/src/coverage.ts","./packages/extension/src/debug.ts","./packages/extension/src/diagnostic.ts","./packages/extension/src/extension.ts","./packages/extension/src/log.ts","./packages/extension/src/polyfills.ts","./packages/extension/src/runner.ts","./packages/extension/src/tagsManager.ts","./packages/extension/src/testTree.ts","./packages/extension/src/testTreeData.ts","./packages/extension/src/utils.ts","./packages/extension/src/watcher.ts","./packages/extension/src/api/child_process.ts","./packages/extension/src/api/pkg.ts","./packages/extension/src/api/resolve.ts","./packages/extension/src/api/rpc.ts","./packages/extension/src/api/terminal.ts","./packages/extension/src/api/types.ts","./packages/extension/src/api/ws.ts","./packages/extension/src/worker/browserSetupFile.ts","./packages/extension/src/worker/index.ts","./packages/shared/src/emitter.ts","./packages/shared/src/index.ts","./packages/shared/src/rpc.ts","./packages/shared/src/utils.ts","./packages/worker/src/coverage.ts","./packages/worker/src/index.ts","./packages/worker/src/reporter.ts","./packages/worker/src/runner.ts","./packages/worker/src/watcher.ts","./packages/worker/src/worker.ts","./packages/worker-legacy/src/collect.ts","./packages/worker-legacy/src/coverage.ts","./packages/worker-legacy/src/index.ts","./packages/worker-legacy/src/reporter.ts","./packages/worker-legacy/src/setupFile.ts","./packages/worker-legacy/src/types.ts","./packages/worker-legacy/src/watcher.ts","./packages/worker-legacy/src/worker.ts"],"version":"5.8.3"}