/** * ds-test-results.js * Test results viewer with polling for Jest/test runner output */ import toolBridge from '../../services/tool-bridge.js'; import { ComponentHelpers } from '../../utils/component-helpers.js'; class DSTestResults extends HTMLElement { constructor() { super(); this.testResults = null; this.isRunning = false; this.pollInterval = null; this.autoRefresh = false; } async connectedCallback() { this.render(); this.setupEventListeners(); await this.loadTestResults(); } disconnectedCallback() { if (this.pollInterval) { clearInterval(this.pollInterval); } } setupEventListeners() { const runBtn = this.querySelector('#run-tests-btn'); if (runBtn) { runBtn.addEventListener('click', () => this.runTests()); } const refreshBtn = this.querySelector('#refresh-tests-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', () => this.loadTestResults()); } const autoRefreshToggle = this.querySelector('#auto-refresh-tests'); if (autoRefreshToggle) { autoRefreshToggle.addEventListener('change', (e) => { this.autoRefresh = e.target.checked; if (this.autoRefresh) { this.pollInterval = setInterval(() => this.loadTestResults(), 3000); } else { if (this.pollInterval) { clearInterval(this.pollInterval); this.pollInterval = null; } } }); } } /** * Load test results from localStorage or file system * In a real implementation, this would call an MCP tool to read test output files */ async loadTestResults() { const content = this.querySelector('#test-results-content'); if (!content) return; try { // Try to load from localStorage (mock data for now) const stored = localStorage.getItem('ds-test-results'); if (stored) { this.testResults = JSON.parse(stored); this.renderResults(); } else { // No results yet content.innerHTML = ComponentHelpers.renderEmpty( 'No test results available. Run tests to see results.', '🧪' ); } } catch (error) { console.error('Failed to load test results:', error); content.innerHTML = ComponentHelpers.renderError('Failed to load test results', error); } } /** * Run tests (would call npm test or similar via MCP) */ async runTests() { if (this.isRunning) return; this.isRunning = true; const runBtn = this.querySelector('#run-tests-btn'); if (runBtn) { runBtn.disabled = true; runBtn.textContent = '🧪 Running Tests...'; } const content = this.querySelector('#test-results-content'); if (content) { content.innerHTML = ComponentHelpers.renderLoading('Running tests...'); } try { // MVP1: Execute real npm test command via MCP // Note: This requires project configuration with test scripts const context = toolBridge.getContext(); // Call backend API to run tests // The backend will execute `npm test` and return parsed results const response = await fetch('/api/test/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectId: context.projectId, testCommand: 'npm test' }) }); if (!response.ok) { throw new Error(`Test execution failed: ${response.statusText}`); } const testResults = await response.json(); // Validate results structure if (!testResults || !testResults.summary) { throw new Error('Invalid test results format'); } this.testResults = { ...testResults, timestamp: new Date().toISOString() }; // Save to localStorage for offline viewing localStorage.setItem('ds-test-results', JSON.stringify(this.testResults)); this.renderResults(); ComponentHelpers.showToast?.( `Tests completed: ${this.testResults.summary.passed}/${this.testResults.summary.total} passed`, this.testResults.summary.failed > 0 ? 'error' : 'success' ); } catch (error) { console.error('Failed to run tests:', error); ComponentHelpers.showToast?.(`Test execution failed: ${error.message}`, 'error'); if (content) { content.innerHTML = ComponentHelpers.renderError('Test execution failed', error); } } finally { this.isRunning = false; if (runBtn) { runBtn.disabled = false; runBtn.textContent = '🧪 Run Tests'; } } } getStatusIcon(status) { const icons = { passed: '✅', failed: '❌', skipped: '⏭️' }; return icons[status] || '⚪'; } getStatusBadge(status) { const types = { passed: 'success', failed: 'error', skipped: 'warning' }; return ComponentHelpers.createBadge(status, types[status] || 'info'); } renderResults() { const content = this.querySelector('#test-results-content'); if (!content || !this.testResults) return; const { summary, suites, coverage, timestamp } = this.testResults; // Calculate pass rate const passRate = ((summary.passed / summary.total) * 100).toFixed(1); content.innerHTML = `