Skip to content

Testing API

The Testing API provides comprehensive testing capabilities for running, managing, and reporting test results within the development environment.

Overview

The Testing API enables you to:

  • Discover and run tests from various testing frameworks
  • Create custom test providers and runners
  • Display test results and coverage information
  • Integrate with continuous integration systems
  • Provide test debugging capabilities
  • Generate test reports and analytics
  • Manage test configurations and environments

Basic Usage

Test Discovery and Running

typescript
import { TraeAPI } from '@trae/api';

// Basic test runner implementation
class TestRunner {
  private testController: TraeAPI.TestController;
  private testData: Map<string, TraeAPI.TestItem> = new Map();

  constructor() {
    this.testController = TraeAPI.tests.createTestController(
      'myTestRunner',
      'My Test Runner'
    );
    
    this.setupTestController();
  }

  private setupTestController() {
    // Handle test run requests
    this.testController.createRunProfile(
      'Run Tests',
      TraeAPI.TestRunProfileKind.Run,
      (request, token) => this.runTests(request, token)
    );

    // Handle debug run requests
    this.testController.createRunProfile(
      'Debug Tests',
      TraeAPI.TestRunProfileKind.Debug,
      (request, token) => this.debugTests(request, token)
    );

    // Handle coverage run requests
    this.testController.createRunProfile(
      'Run with Coverage',
      TraeAPI.TestRunProfileKind.Coverage,
      (request, token) => this.runTestsWithCoverage(request, token)
    );

    // Refresh tests when files change
    this.testController.refreshHandler = () => this.discoverTests();

    // Resolve test items
    this.testController.resolveHandler = (item) => this.resolveTestItem(item);
  }

  // Discover tests in the workspace
  async discoverTests(): Promise<void> {
    console.log('Discovering tests...');
    
    // Find test files
    const testFiles = await TraeAPI.workspace.findFiles(
      '**/*.{test,spec}.{js,ts}',
      '**/node_modules/**'
    );

    // Clear existing tests
    this.testController.items.replace([]);
    this.testData.clear();

    // Process each test file
    for (const file of testFiles) {
      await this.processTestFile(file);
    }

    console.log(`Discovered ${this.testData.size} tests`);
  }

  private async processTestFile(uri: TraeAPI.Uri): Promise<void> {
    try {
      const document = await TraeAPI.workspace.openTextDocument(uri);
      const text = document.getText();
      
      // Parse test file to find test cases
      const tests = this.parseTestFile(text, uri);
      
      if (tests.length > 0) {
        // Create file-level test item
        const fileItem = this.testController.createTestItem(
          uri.toString(),
          this.getRelativePath(uri),
          uri
        );
        
        // Add individual test cases
        for (const test of tests) {
          const testItem = this.testController.createTestItem(
            `${uri.toString()}::${test.name}`,
            test.name,
            uri
          );
          
          testItem.range = test.range;
          testItem.canResolveChildren = false;
          
          fileItem.children.add(testItem);
          this.testData.set(testItem.id, testItem);
        }
        
        this.testController.items.add(fileItem);
        this.testData.set(fileItem.id, fileItem);
      }
    } catch (error) {
      console.error('Error processing test file:', uri.toString(), error);
    }
  }

  private parseTestFile(content: string, uri: TraeAPI.Uri): Array<{ name: string; range: TraeAPI.Range }> {
    const tests: Array<{ name: string; range: TraeAPI.Range }> = [];
    const lines = content.split('\n');
    
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      
      // Match test patterns (Jest, Mocha, etc.)
      const testMatch = line.match(/(?:test|it)\s*\(\s*['"`]([^'"`)]+)['"`]/);
      if (testMatch) {
        const testName = testMatch[1];
        const range = new TraeAPI.Range(
          new TraeAPI.Position(i, 0),
          new TraeAPI.Position(i, line.length)
        );
        
        tests.push({ name: testName, range });
      }
      
      // Match describe blocks
      const describeMatch = line.match(/describe\s*\(\s*['"`]([^'"`)]+)['"`]/);
      if (describeMatch) {
        const suiteName = describeMatch[1];
        const range = new TraeAPI.Range(
          new TraeAPI.Position(i, 0),
          new TraeAPI.Position(i, line.length)
        );
        
        tests.push({ name: `Suite: ${suiteName}`, range });
      }
    }
    
    return tests;
  }

  private getRelativePath(uri: TraeAPI.Uri): string {
    const workspaceFolder = TraeAPI.workspace.getWorkspaceFolder(uri);
    if (workspaceFolder) {
      return TraeAPI.workspace.asRelativePath(uri, false);
    }
    return uri.fsPath;
  }

  // Run tests
  private async runTests(
    request: TraeAPI.TestRunRequest,
    token: TraeAPI.CancellationToken
  ): Promise<void> {
    const run = this.testController.createTestRun(request);
    
    try {
      const testsToRun = this.getTestsToRun(request);
      
      for (const test of testsToRun) {
        if (token.isCancellationRequested) {
          break;
        }
        
        await this.runSingleTest(test, run);
      }
    } catch (error) {
      console.error('Error running tests:', error);
    } finally {
      run.end();
    }
  }

  private async runSingleTest(test: TraeAPI.TestItem, run: TraeAPI.TestRun): Promise<void> {
    run.started(test);
    
    try {
      // Simulate test execution
      const result = await this.executeTest(test);
      
      if (result.success) {
        run.passed(test, result.duration);
      } else {
        const message = new TraeAPI.TestMessage(result.error || 'Test failed');
        if (result.location) {
          message.location = result.location;
        }
        run.failed(test, message, result.duration);
      }
    } catch (error) {
      const message = new TraeAPI.TestMessage(`Test execution error: ${error.message}`);
      run.errored(test, message);
    }
  }

  private async executeTest(test: TraeAPI.TestItem): Promise<{
    success: boolean;
    duration?: number;
    error?: string;
    location?: TraeAPI.Location;
  }> {
    // Mock test execution - replace with actual test runner integration
    const startTime = Date.now();
    
    // Simulate test execution time
    await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
    
    const duration = Date.now() - startTime;
    const success = Math.random() > 0.2; // 80% success rate for demo
    
    if (!success) {
      return {
        success: false,
        duration,
        error: 'Assertion failed: expected true but got false',
        location: test.uri ? new TraeAPI.Location(test.uri, test.range || new TraeAPI.Position(0, 0)) : undefined
      };
    }
    
    return { success: true, duration };
  }

  private getTestsToRun(request: TraeAPI.TestRunRequest): TraeAPI.TestItem[] {
    const tests: TraeAPI.TestItem[] = [];
    
    if (request.include) {
      // Run specific tests
      for (const test of request.include) {
        tests.push(test);
        // Include children if it's a suite
        test.children.forEach(child => tests.push(child));
      }
    } else {
      // Run all tests
      this.testController.items.forEach(item => {
        tests.push(item);
        item.children.forEach(child => tests.push(child));
      });
    }
    
    // Exclude specified tests
    if (request.exclude) {
      const excludeIds = new Set(request.exclude.map(t => t.id));
      return tests.filter(t => !excludeIds.has(t.id));
    }
    
    return tests;
  }

  // Debug tests
  private async debugTests(
    request: TraeAPI.TestRunRequest,
    token: TraeAPI.CancellationToken
  ): Promise<void> {
    const testsToDebug = this.getTestsToRun(request);
    
    if (testsToDebug.length === 0) {
      TraeAPI.window.showWarningMessage('No tests selected for debugging');
      return;
    }
    
    // Create debug configuration for tests
    const debugConfig: TraeAPI.DebugConfiguration = {
      type: 'node',
      request: 'launch',
      name: 'Debug Tests',
      program: '${workspaceFolder}/node_modules/.bin/jest',
      args: [
        '--runInBand',
        '--testNamePattern',
        testsToDebug.map(t => t.label).join('|')
      ],
      console: 'integratedTerminal',
      internalConsoleOptions: 'neverOpen'
    };
    
    // Start debugging session
    const success = await TraeAPI.debug.startDebugging(
      TraeAPI.workspace.workspaceFolders?.[0],
      debugConfig
    );
    
    if (!success) {
      TraeAPI.window.showErrorMessage('Failed to start debug session for tests');
    }
  }

  // Run tests with coverage
  private async runTestsWithCoverage(
    request: TraeAPI.TestRunRequest,
    token: TraeAPI.CancellationToken
  ): Promise<void> {
    const run = this.testController.createTestRun(request);
    
    try {
      // Run tests and collect coverage
      const testsToRun = this.getTestsToRun(request);
      const coverageData = new Map<string, TraeAPI.FileCoverage>();
      
      for (const test of testsToRun) {
        if (token.isCancellationRequested) {
          break;
        }
        
        const result = await this.runSingleTestWithCoverage(test, run, coverageData);
      }
      
      // Set coverage data
      for (const [uri, coverage] of coverageData) {
        run.addCoverage(coverage);
      }
    } catch (error) {
      console.error('Error running tests with coverage:', error);
    } finally {
      run.end();
    }
  }

  private async runSingleTestWithCoverage(
    test: TraeAPI.TestItem,
    run: TraeAPI.TestRun,
    coverageData: Map<string, TraeAPI.FileCoverage>
  ): Promise<void> {
    await this.runSingleTest(test, run);
    
    // Mock coverage data collection
    if (test.uri) {
      const uri = test.uri.toString();
      if (!coverageData.has(uri)) {
        const coverage = this.generateMockCoverage(test.uri);
        coverageData.set(uri, coverage);
      }
    }
  }

  private generateMockCoverage(uri: TraeAPI.Uri): TraeAPI.FileCoverage {
    // Generate mock coverage data
    const statementCoverage: TraeAPI.StatementCoverage[] = [];
    const branchCoverage: TraeAPI.BranchCoverage[] = [];
    const functionCoverage: TraeAPI.FunctionCoverage[] = [];
    
    // Mock some coverage data
    for (let i = 0; i < 50; i++) {
      const range = new TraeAPI.Range(
        new TraeAPI.Position(i, 0),
        new TraeAPI.Position(i, 20)
      );
      
      statementCoverage.push(
        new TraeAPI.StatementCoverage(
          Math.random() > 0.3 ? 1 : 0, // 70% coverage
          new TraeAPI.Location(uri, range)
        )
      );
    }
    
    return TraeAPI.FileCoverage.fromDetails(
      uri,
      statementCoverage,
      branchCoverage,
      functionCoverage
    );
  }

  private async resolveTestItem(item: TraeAPI.TestItem | undefined): Promise<void> {
    if (!item) {
      // Resolve root items
      await this.discoverTests();
      return;
    }
    
    // Resolve children for specific item if needed
    if (item.canResolveChildren) {
      // Implementation for resolving children
    }
  }

  dispose(): void {
    this.testController.dispose();
  }
}

// Initialize test runner
const testRunner = new TestRunner();

Test Result Management

typescript
class TestResultManager {
  private testResults: Map<string, TestResult> = new Map();
  private testHistory: TestResult[][] = [];
  private maxHistorySize = 50;

  constructor() {
    this.setupResultListeners();
  }

  private setupResultListeners() {
    // Listen for test run completion
    TraeAPI.tests.onDidChangeTestResults(results => {
      this.processTestResults(results);
    });
  }

  private processTestResults(results: readonly TraeAPI.TestResult[]): void {
    const currentRun: TestResult[] = [];
    
    for (const result of results) {
      const testResult: TestResult = {
        id: result.test.id,
        name: result.test.label,
        state: this.mapTestState(result.state),
        duration: result.duration,
        message: result.messages?.[0]?.message,
        location: result.messages?.[0]?.location,
        timestamp: Date.now()
      };
      
      this.testResults.set(result.test.id, testResult);
      currentRun.push(testResult);
    }
    
    // Add to history
    this.testHistory.unshift(currentRun);
    if (this.testHistory.length > this.maxHistorySize) {
      this.testHistory.pop();
    }
    
    // Generate summary
    this.generateTestSummary(currentRun);
  }

  private mapTestState(state: TraeAPI.TestResultState): 'passed' | 'failed' | 'skipped' | 'errored' {
    switch (state) {
      case TraeAPI.TestResultState.Passed:
        return 'passed';
      case TraeAPI.TestResultState.Failed:
        return 'failed';
      case TraeAPI.TestResultState.Skipped:
        return 'skipped';
      case TraeAPI.TestResultState.Errored:
        return 'errored';
      default:
        return 'failed';
    }
  }

  private generateTestSummary(results: TestResult[]): void {
    const summary = {
      total: results.length,
      passed: results.filter(r => r.state === 'passed').length,
      failed: results.filter(r => r.state === 'failed').length,
      skipped: results.filter(r => r.state === 'skipped').length,
      errored: results.filter(r => r.state === 'errored').length,
      duration: results.reduce((sum, r) => sum + (r.duration || 0), 0)
    };
    
    console.log('Test Summary:', summary);
    
    // Show notification for failed tests
    if (summary.failed > 0 || summary.errored > 0) {
      TraeAPI.window.showWarningMessage(
        `Tests completed: ${summary.passed} passed, ${summary.failed + summary.errored} failed`
      );
    } else {
      TraeAPI.window.showInformationMessage(
        `All ${summary.total} tests passed! ✅`
      );
    }
  }

  // Get test results
  getTestResult(testId: string): TestResult | undefined {
    return this.testResults.get(testId);
  }

  getAllTestResults(): TestResult[] {
    return Array.from(this.testResults.values());
  }

  getTestHistory(): TestResult[][] {
    return [...this.testHistory];
  }

  // Get test statistics
  getTestStatistics(): TestStatistics {
    const allResults = this.getAllTestResults();
    
    return {
      totalTests: allResults.length,
      passedTests: allResults.filter(r => r.state === 'passed').length,
      failedTests: allResults.filter(r => r.state === 'failed').length,
      skippedTests: allResults.filter(r => r.state === 'skipped').length,
      erroredTests: allResults.filter(r => r.state === 'errored').length,
      averageDuration: allResults.reduce((sum, r) => sum + (r.duration || 0), 0) / allResults.length,
      successRate: (allResults.filter(r => r.state === 'passed').length / allResults.length) * 100
    };
  }

  // Export test results
  exportResults(format: 'json' | 'xml' | 'html' = 'json'): string {
    const results = this.getAllTestResults();
    
    switch (format) {
      case 'json':
        return JSON.stringify(results, null, 2);
      case 'xml':
        return this.generateXMLReport(results);
      case 'html':
        return this.generateHTMLReport(results);
      default:
        return JSON.stringify(results, null, 2);
    }
  }

  private generateXMLReport(results: TestResult[]): string {
    const statistics = this.getTestStatistics();
    
    let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
    xml += `<testsuites tests="${statistics.totalTests}" failures="${statistics.failedTests}" errors="${statistics.erroredTests}" time="${statistics.averageDuration / 1000}">\n`;
    
    const groupedResults = this.groupResultsByFile(results);
    
    for (const [file, fileResults] of groupedResults) {
      xml += `  <testsuite name="${file}" tests="${fileResults.length}">\n`;
      
      for (const result of fileResults) {
        xml += `    <testcase name="${result.name}" time="${(result.duration || 0) / 1000}">\n`;
        
        if (result.state === 'failed' || result.state === 'errored') {
          xml += `      <failure message="${this.escapeXML(result.message || 'Test failed')}"></failure>\n`;
        } else if (result.state === 'skipped') {
          xml += `      <skipped></skipped>\n`;
        }
        
        xml += `    </testcase>\n`;
      }
      
      xml += `  </testsuite>\n`;
    }
    
    xml += '</testsuites>';
    return xml;
  }

  private generateHTMLReport(results: TestResult[]): string {
    const statistics = this.getTestStatistics();
    
    let html = `
    <!DOCTYPE html>
    <html>
    <head>
      <title>Test Results</title>
      <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .summary { background: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
        .test-item { margin: 10px 0; padding: 10px; border-left: 4px solid #ccc; }
        .passed { border-left-color: #4CAF50; background: #f1f8e9; }
        .failed { border-left-color: #f44336; background: #ffebee; }
        .skipped { border-left-color: #ff9800; background: #fff3e0; }
        .errored { border-left-color: #9c27b0; background: #f3e5f5; }
        .test-name { font-weight: bold; }
        .test-duration { color: #666; font-size: 0.9em; }
        .test-message { color: #d32f2f; margin-top: 5px; font-family: monospace; }
      </style>
    </head>
    <body>
      <h1>Test Results Report</h1>
      
      <div class="summary">
        <h2>Summary</h2>
        <p>Total Tests: ${statistics.totalTests}</p>
        <p>Passed: ${statistics.passedTests}</p>
        <p>Failed: ${statistics.failedTests}</p>
        <p>Skipped: ${statistics.skippedTests}</p>
        <p>Errored: ${statistics.erroredTests}</p>
        <p>Success Rate: ${statistics.successRate.toFixed(2)}%</p>
        <p>Average Duration: ${statistics.averageDuration.toFixed(2)}ms</p>
      </div>
      
      <h2>Test Details</h2>
    `;
    
    for (const result of results) {
      html += `
      <div class="test-item ${result.state}">
        <div class="test-name">${result.name}</div>
        <div class="test-duration">Duration: ${result.duration || 0}ms</div>
        ${result.message ? `<div class="test-message">${this.escapeHTML(result.message)}</div>` : ''}
      </div>
      `;
    }
    
    html += `
    </body>
    </html>
    `;
    
    return html;
  }

  private groupResultsByFile(results: TestResult[]): Map<string, TestResult[]> {
    const grouped = new Map<string, TestResult[]>();
    
    for (const result of results) {
      const file = result.location?.uri.fsPath || 'Unknown';
      const existing = grouped.get(file) || [];
      existing.push(result);
      grouped.set(file, existing);
    }
    
    return grouped;
  }

  private escapeXML(text: string): string {
    return text
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  private escapeHTML(text: string): string {
    return text
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }
}

interface TestResult {
  id: string;
  name: string;
  state: 'passed' | 'failed' | 'skipped' | 'errored';
  duration?: number;
  message?: string;
  location?: TraeAPI.Location;
  timestamp: number;
}

interface TestStatistics {
  totalTests: number;
  passedTests: number;
  failedTests: number;
  skippedTests: number;
  erroredTests: number;
  averageDuration: number;
  successRate: number;
}

// Initialize test result manager
const testResultManager = new TestResultManager();

Test Coverage Analysis

typescript
class TestCoverageAnalyzer {
  private coverageData: Map<string, FileCoverageData> = new Map();
  private coverageThresholds = {
    statements: 80,
    branches: 75,
    functions: 85,
    lines: 80
  };

  constructor() {
    this.setupCoverageListeners();
  }

  private setupCoverageListeners() {
    // Listen for coverage data updates
    TraeAPI.tests.onDidChangeTestResults(results => {
      this.processCoverageData(results);
    });
  }

  private processCoverageData(results: readonly TraeAPI.TestResult[]): void {
    // Extract coverage data from test results
    for (const result of results) {
      if (result.coverage) {
        for (const fileCoverage of result.coverage) {
          this.analyzFileCoverage(fileCoverage);
        }
      }
    }
    
    this.generateCoverageReport();
  }

  private analyzFileCoverage(fileCoverage: TraeAPI.FileCoverage): void {
    const uri = fileCoverage.uri.toString();
    
    const analysis: FileCoverageData = {
      uri: fileCoverage.uri,
      statements: this.analyzeStatementCoverage(fileCoverage.statementCoverage),
      branches: this.analyzeBranchCoverage(fileCoverage.branchCoverage),
      functions: this.analyzeFunctionCoverage(fileCoverage.functionCoverage),
      lines: this.analyzeLineCoverage(fileCoverage)
    };
    
    this.coverageData.set(uri, analysis);
  }

  private analyzeStatementCoverage(statements: readonly TraeAPI.StatementCoverage[]): CoverageMetrics {
    const total = statements.length;
    const covered = statements.filter(s => s.executed > 0).length;
    
    return {
      total,
      covered,
      percentage: total > 0 ? (covered / total) * 100 : 0,
      uncoveredRanges: statements
        .filter(s => s.executed === 0)
        .map(s => s.location.range)
    };
  }

  private analyzeBranchCoverage(branches: readonly TraeAPI.BranchCoverage[]): CoverageMetrics {
    const total = branches.length;
    const covered = branches.filter(b => b.executed > 0).length;
    
    return {
      total,
      covered,
      percentage: total > 0 ? (covered / total) * 100 : 0,
      uncoveredRanges: branches
        .filter(b => b.executed === 0)
        .map(b => b.location.range)
    };
  }

  private analyzeFunctionCoverage(functions: readonly TraeAPI.FunctionCoverage[]): CoverageMetrics {
    const total = functions.length;
    const covered = functions.filter(f => f.executed > 0).length;
    
    return {
      total,
      covered,
      percentage: total > 0 ? (covered / total) * 100 : 0,
      uncoveredRanges: functions
        .filter(f => f.executed === 0)
        .map(f => f.location.range)
    };
  }

  private analyzeLineCoverage(fileCoverage: TraeAPI.FileCoverage): CoverageMetrics {
    // Calculate line coverage from statement coverage
    const lineMap = new Map<number, boolean>();
    
    for (const statement of fileCoverage.statementCoverage) {
      const line = statement.location.range.start.line;
      if (!lineMap.has(line)) {
        lineMap.set(line, statement.executed > 0);
      } else {
        // If any statement on the line is covered, mark line as covered
        lineMap.set(line, lineMap.get(line)! || statement.executed > 0);
      }
    }
    
    const total = lineMap.size;
    const covered = Array.from(lineMap.values()).filter(Boolean).length;
    
    return {
      total,
      covered,
      percentage: total > 0 ? (covered / total) * 100 : 0,
      uncoveredRanges: Array.from(lineMap.entries())
        .filter(([line, isCovered]) => !isCovered)
        .map(([line]) => new TraeAPI.Range(
          new TraeAPI.Position(line, 0),
          new TraeAPI.Position(line, Number.MAX_SAFE_INTEGER)
        ))
    };
  }

  private generateCoverageReport(): void {
    const overallCoverage = this.calculateOverallCoverage();
    
    console.log('Coverage Report:', {
      overall: overallCoverage,
      thresholds: this.coverageThresholds,
      files: this.coverageData.size
    });
    
    // Check thresholds
    this.checkCoverageThresholds(overallCoverage);
  }

  private calculateOverallCoverage(): OverallCoverage {
    const files = Array.from(this.coverageData.values());
    
    if (files.length === 0) {
      return {
        statements: { total: 0, covered: 0, percentage: 0 },
        branches: { total: 0, covered: 0, percentage: 0 },
        functions: { total: 0, covered: 0, percentage: 0 },
        lines: { total: 0, covered: 0, percentage: 0 }
      };
    }
    
    const totals = files.reduce(
      (acc, file) => ({
        statements: {
          total: acc.statements.total + file.statements.total,
          covered: acc.statements.covered + file.statements.covered
        },
        branches: {
          total: acc.branches.total + file.branches.total,
          covered: acc.branches.covered + file.branches.covered
        },
        functions: {
          total: acc.functions.total + file.functions.total,
          covered: acc.functions.covered + file.functions.covered
        },
        lines: {
          total: acc.lines.total + file.lines.total,
          covered: acc.lines.covered + file.lines.covered
        }
      }),
      {
        statements: { total: 0, covered: 0 },
        branches: { total: 0, covered: 0 },
        functions: { total: 0, covered: 0 },
        lines: { total: 0, covered: 0 }
      }
    );
    
    return {
      statements: {
        ...totals.statements,
        percentage: totals.statements.total > 0 ? (totals.statements.covered / totals.statements.total) * 100 : 0
      },
      branches: {
        ...totals.branches,
        percentage: totals.branches.total > 0 ? (totals.branches.covered / totals.branches.total) * 100 : 0
      },
      functions: {
        ...totals.functions,
        percentage: totals.functions.total > 0 ? (totals.functions.covered / totals.functions.total) * 100 : 0
      },
      lines: {
        ...totals.lines,
        percentage: totals.lines.total > 0 ? (totals.lines.covered / totals.lines.total) * 100 : 0
      }
    };
  }

  private checkCoverageThresholds(coverage: OverallCoverage): void {
    const failures: string[] = [];
    
    if (coverage.statements.percentage < this.coverageThresholds.statements) {
      failures.push(`Statements: ${coverage.statements.percentage.toFixed(2)}% < ${this.coverageThresholds.statements}%`);
    }
    
    if (coverage.branches.percentage < this.coverageThresholds.branches) {
      failures.push(`Branches: ${coverage.branches.percentage.toFixed(2)}% < ${this.coverageThresholds.branches}%`);
    }
    
    if (coverage.functions.percentage < this.coverageThresholds.functions) {
      failures.push(`Functions: ${coverage.functions.percentage.toFixed(2)}% < ${this.coverageThresholds.functions}%`);
    }
    
    if (coverage.lines.percentage < this.coverageThresholds.lines) {
      failures.push(`Lines: ${coverage.lines.percentage.toFixed(2)}% < ${this.coverageThresholds.lines}%`);
    }
    
    if (failures.length > 0) {
      TraeAPI.window.showWarningMessage(
        `Coverage thresholds not met:\n${failures.join('\n')}`
      );
    } else {
      TraeAPI.window.showInformationMessage('All coverage thresholds met! 🎯');
    }
  }

  // Get coverage data
  getCoverageForFile(uri: TraeAPI.Uri): FileCoverageData | undefined {
    return this.coverageData.get(uri.toString());
  }

  getAllCoverageData(): FileCoverageData[] {
    return Array.from(this.coverageData.values());
  }

  getOverallCoverage(): OverallCoverage {
    return this.calculateOverallCoverage();
  }

  // Set coverage thresholds
  setCoverageThresholds(thresholds: Partial<typeof this.coverageThresholds>): void {
    this.coverageThresholds = { ...this.coverageThresholds, ...thresholds };
  }

  // Export coverage report
  exportCoverageReport(format: 'json' | 'lcov' | 'html' = 'json'): string {
    const coverage = this.getOverallCoverage();
    const files = this.getAllCoverageData();
    
    switch (format) {
      case 'json':
        return JSON.stringify({ overall: coverage, files }, null, 2);
      case 'lcov':
        return this.generateLCOVReport(files);
      case 'html':
        return this.generateHTMLCoverageReport(coverage, files);
      default:
        return JSON.stringify({ overall: coverage, files }, null, 2);
    }
  }

  private generateLCOVReport(files: FileCoverageData[]): string {
    let lcov = '';
    
    for (const file of files) {
      lcov += `SF:${file.uri.fsPath}\n`;
      
      // Function coverage
      lcov += `FNF:${file.functions.total}\n`;
      lcov += `FNH:${file.functions.covered}\n`;
      
      // Line coverage
      lcov += `LF:${file.lines.total}\n`;
      lcov += `LH:${file.lines.covered}\n`;
      
      // Branch coverage
      lcov += `BRF:${file.branches.total}\n`;
      lcov += `BRH:${file.branches.covered}\n`;
      
      lcov += 'end_of_record\n';
    }
    
    return lcov;
  }

  private generateHTMLCoverageReport(overall: OverallCoverage, files: FileCoverageData[]): string {
    return `
    <!DOCTYPE html>
    <html>
    <head>
      <title>Coverage Report</title>
      <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .summary { background: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
        .metric { display: inline-block; margin: 10px; padding: 10px; border-radius: 5px; min-width: 120px; text-align: center; }
        .high { background: #4CAF50; color: white; }
        .medium { background: #ff9800; color: white; }
        .low { background: #f44336; color: white; }
        .file-item { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
        .file-name { font-weight: bold; margin-bottom: 5px; }
        .coverage-bar { height: 20px; background: #f0f0f0; border-radius: 10px; overflow: hidden; }
        .coverage-fill { height: 100%; background: #4CAF50; transition: width 0.3s; }
      </style>
    </head>
    <body>
      <h1>Test Coverage Report</h1>
      
      <div class="summary">
        <h2>Overall Coverage</h2>
        <div class="metric ${this.getCoverageClass(overall.statements.percentage)}">
          <div>Statements</div>
          <div>${overall.statements.percentage.toFixed(2)}%</div>
          <div>${overall.statements.covered}/${overall.statements.total}</div>
        </div>
        <div class="metric ${this.getCoverageClass(overall.branches.percentage)}">
          <div>Branches</div>
          <div>${overall.branches.percentage.toFixed(2)}%</div>
          <div>${overall.branches.covered}/${overall.branches.total}</div>
        </div>
        <div class="metric ${this.getCoverageClass(overall.functions.percentage)}">
          <div>Functions</div>
          <div>${overall.functions.percentage.toFixed(2)}%</div>
          <div>${overall.functions.covered}/${overall.functions.total}</div>
        </div>
        <div class="metric ${this.getCoverageClass(overall.lines.percentage)}">
          <div>Lines</div>
          <div>${overall.lines.percentage.toFixed(2)}%</div>
          <div>${overall.lines.covered}/${overall.lines.total}</div>
        </div>
      </div>
      
      <h2>File Coverage</h2>
      ${files.map(file => `
        <div class="file-item">
          <div class="file-name">${file.uri.fsPath}</div>
          <div>Statements: ${file.statements.percentage.toFixed(2)}%</div>
          <div class="coverage-bar">
            <div class="coverage-fill" style="width: ${file.statements.percentage}%"></div>
          </div>
        </div>
      `).join('')}
    </body>
    </html>
    `;
  }

  private getCoverageClass(percentage: number): string {
    if (percentage >= 80) return 'high';
    if (percentage >= 60) return 'medium';
    return 'low';
  }
}

interface CoverageMetrics {
  total: number;
  covered: number;
  percentage: number;
  uncoveredRanges: TraeAPI.Range[];
}

interface FileCoverageData {
  uri: TraeAPI.Uri;
  statements: CoverageMetrics;
  branches: CoverageMetrics;
  functions: CoverageMetrics;
  lines: CoverageMetrics;
}

interface OverallCoverage {
  statements: { total: number; covered: number; percentage: number };
  branches: { total: number; covered: number; percentage: number };
  functions: { total: number; covered: number; percentage: number };
  lines: { total: number; covered: number; percentage: number };
}

// Initialize coverage analyzer
const coverageAnalyzer = new TestCoverageAnalyzer();

API Reference

Core Interfaces

typescript
interface TestController {
  readonly id: string;
  readonly label: string;
  readonly items: TestItemCollection;
  
  createTestItem(id: string, label: string, uri?: Uri): TestItem;
  createTestRun(request: TestRunRequest, name?: string, persist?: boolean): TestRun;
  createRunProfile(
    label: string,
    kind: TestRunProfileKind,
    runHandler: (request: TestRunRequest, token: CancellationToken) => void | Thenable<void>,
    isDefault?: boolean
  ): TestRunProfile;
  
  refreshHandler?: () => void | Thenable<void>;
  resolveHandler?: (item: TestItem | undefined) => void | Thenable<void>;
  
  dispose(): void;
}

interface TestItem {
  readonly id: string;
  label: string;
  uri?: Uri;
  children: TestItemCollection;
  parent?: TestItem;
  tags: readonly TestTag[];
  canResolveChildren: boolean;
  busy: boolean;
  range?: Range;
  error?: string | MarkdownString;
}

interface TestRun {
  readonly name?: string;
  readonly token: CancellationToken;
  
  enqueued(test: TestItem): void;
  started(test: TestItem): void;
  skipped(test: TestItem): void;
  failed(test: TestItem, message: TestMessage | readonly TestMessage[], duration?: number): void;
  errored(test: TestItem, message: TestMessage | readonly TestMessage[], duration?: number): void;
  passed(test: TestItem, duration?: number): void;
  
  appendOutput(output: string, location?: Location, test?: TestItem): void;
  addCoverage(fileCoverage: FileCoverage): void;
  
  end(): void;
}

interface TestRunRequest {
  readonly include?: readonly TestItem[];
  readonly exclude?: readonly TestItem[];
  readonly profile?: TestRunProfile;
}

interface FileCoverage {
  readonly uri: Uri;
  readonly statementCoverage: readonly StatementCoverage[];
  readonly branchCoverage: readonly BranchCoverage[];
  readonly functionCoverage: readonly FunctionCoverage[];
  
  static fromDetails(
    uri: Uri,
    statementCoverage: readonly StatementCoverage[],
    branchCoverage?: readonly BranchCoverage[],
    functionCoverage?: readonly FunctionCoverage[]
  ): FileCoverage;
}

Best Practices

  1. Test Discovery: Implement efficient test discovery with file watching
  2. Performance: Use lazy loading and caching for large test suites
  3. User Experience: Provide clear progress indicators and error messages
  4. Integration: Support multiple testing frameworks and runners
  5. Coverage: Implement comprehensive coverage analysis
  6. Reporting: Generate detailed and actionable test reports
  7. Debugging: Provide seamless test debugging capabilities
  8. CI/CD: Integrate with continuous integration systems

Your Ultimate AI-Powered IDE Learning Guide