Spaces:
Running
Running
import { Component, inject } from '@angular/core'; | |
import { CommonModule } from '@angular/common'; | |
import { FormsModule } from '@angular/forms'; | |
import { ApiService } from '../../services/api.service'; | |
interface TestResult { | |
name: string; | |
status: 'PASS' | 'FAIL' | 'RUNNING'; | |
duration_ms?: number; | |
error?: string; | |
} | |
interface TestRun { | |
test_type: string; | |
start_time: string; | |
tests: TestResult[]; | |
summary: { | |
total: number; | |
passed: number; | |
failed: number; | |
duration_ms: number; | |
}; | |
} | |
({ | |
selector: 'app-test', | |
standalone: true, | |
imports: [CommonModule, FormsModule], | |
template: ` | |
<div class="test-container"> | |
<h2>System Tests</h2> | |
<div class="test-controls"> | |
<button class="btn btn-primary" (click)="runAllTests()" [disabled]="running"> | |
Run All Tests | |
</button> | |
<button class="btn btn-secondary" (click)="runSelectedTests()" [disabled]="running || selectedTests.length === 0"> | |
Run Selected | |
</button> | |
<button class="btn btn-danger" (click)="stopTests()" [disabled]="!running"> | |
Stop | |
</button> | |
</div> | |
<div class="test-categories"> | |
<div class="category"> | |
<label> | |
<input type="checkbox" [(ngModel)]="allSelected" (change)="toggleAll()"> | |
<strong>All Tests</strong> | |
</label> | |
</div> | |
<div class="category"> | |
<label> | |
<input type="checkbox" [(ngModel)]="categories.ui" (change)="updateSelection()"> | |
UI Tests (15 tests) | |
</label> | |
<div class="sub-tests" *ngIf="categories.ui"> | |
<label><input type="checkbox"> Login Flow</label> | |
<label><input type="checkbox"> Project CRUD</label> | |
<label><input type="checkbox"> Version Management</label> | |
</div> | |
</div> | |
<div class="category"> | |
<label> | |
<input type="checkbox" [(ngModel)]="categories.backend" (change)="updateSelection()"> | |
Backend Tests (22 tests) | |
</label> | |
<div class="sub-tests" *ngIf="categories.backend"> | |
<label><input type="checkbox"> Authentication</label> | |
<label><input type="checkbox"> API Endpoints</label> | |
<label><input type="checkbox"> Race Conditions</label> | |
</div> | |
</div> | |
<div class="category"> | |
<label> | |
<input type="checkbox" [(ngModel)]="categories.integration" (change)="updateSelection()"> | |
Integration Tests (18 tests) | |
</label> | |
</div> | |
<div class="category"> | |
<label> | |
<input type="checkbox" [(ngModel)]="categories.spark" (change)="updateSelection()"> | |
Spark Tests (8 tests) | |
</label> | |
</div> | |
</div> | |
@if (currentRun) { | |
<div class="test-results"> | |
<h3>Test Results:</h3> | |
<div class="results-list"> | |
@for (test of currentRun.tests; track test.name) { | |
<div class="test-result" [class.pass]="test.status === 'PASS'" [class.fail]="test.status === 'FAIL'"> | |
@if (test.status === 'PASS') { | |
<span class="status">β</span> | |
} @else if (test.status === 'FAIL') { | |
<span class="status">β</span> | |
} @else { | |
<span class="status spinner"></span> | |
} | |
<span class="name">{{ test.name }}</span> | |
@if (test.duration_ms) { | |
<span class="duration">{{ test.duration_ms }}ms</span> | |
} | |
@if (test.error) { | |
<div class="error">{{ test.error }}</div> | |
} | |
</div> | |
} | |
</div> | |
@if (!running && currentRun.summary) { | |
<div class="test-summary"> | |
<div class="progress-bar"> | |
<div | |
class="progress-fill" | |
[style.width.%]="(currentRun.summary.passed / currentRun.summary.total) * 100" | |
[class.success]="currentRun.summary.failed === 0" | |
[class.warning]="currentRun.summary.failed > 0" | |
></div> | |
</div> | |
<div class="summary-text"> | |
Progress: {{ currentRun.summary.passed + currentRun.summary.failed }}/{{ currentRun.summary.total }} | |
({{ ((currentRun.summary.passed / currentRun.summary.total) * 100).toFixed(0) }}%) | |
</div> | |
<div class="summary-stats"> | |
Passed: {{ currentRun.summary.passed }} | | |
Failed: {{ currentRun.summary.failed }} | | |
Total time: {{ (currentRun.summary.duration_ms / 1000).toFixed(1) }}s | |
</div> | |
</div> | |
} | |
</div> | |
} | |
@if (!currentRun && !running) { | |
<div class="empty-state"> | |
<p>No test results yet. Click "Run All Tests" to start.</p> | |
</div> | |
} | |
</div> | |
`, | |
styles: [` | |
.test-container { | |
h2 { | |
margin-bottom: 1.5rem; | |
} | |
} | |
.test-controls { | |
display: flex; | |
gap: 0.5rem; | |
margin-bottom: 1.5rem; | |
} | |
.test-categories { | |
background: white; | |
border: 1px solid #dee2e6; | |
border-radius: 0.25rem; | |
padding: 1rem; | |
margin-bottom: 1.5rem; | |
.category { | |
margin-bottom: 0.5rem; | |
label { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
cursor: pointer; | |
input[type="checkbox"] { | |
cursor: pointer; | |
} | |
} | |
} | |
.sub-tests { | |
margin-left: 1.5rem; | |
margin-top: 0.5rem; | |
label { | |
display: block; | |
margin-bottom: 0.25rem; | |
font-weight: normal; | |
color: #6c757d; | |
} | |
} | |
} | |
.test-results { | |
background: white; | |
border: 1px solid #dee2e6; | |
border-radius: 0.25rem; | |
padding: 1rem; | |
h3 { | |
margin-top: 0; | |
margin-bottom: 1rem; | |
} | |
.results-list { | |
max-height: 400px; | |
overflow-y: auto; | |
margin-bottom: 1rem; | |
} | |
.test-result { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
padding: 0.5rem; | |
border-bottom: 1px solid #f0f0f0; | |
&.pass .status { color: #28a745; } | |
&.fail .status { color: #dc3545; } | |
.status { | |
width: 20px; | |
text-align: center; | |
} | |
.name { | |
flex: 1; | |
} | |
.duration { | |
color: #6c757d; | |
font-size: 0.875rem; | |
} | |
.error { | |
width: 100%; | |
margin-top: 0.5rem; | |
padding: 0.5rem; | |
background-color: #f8d7da; | |
color: #721c24; | |
border-radius: 0.25rem; | |
font-size: 0.875rem; | |
} | |
} | |
} | |
.test-summary { | |
border-top: 1px solid #dee2e6; | |
padding-top: 1rem; | |
.progress-bar { | |
height: 20px; | |
background-color: #e9ecef; | |
border-radius: 0.25rem; | |
overflow: hidden; | |
margin-bottom: 0.5rem; | |
.progress-fill { | |
height: 100%; | |
transition: width 0.3s ease; | |
&.success { background-color: #28a745; } | |
&.warning { background-color: #ffc107; } | |
} | |
} | |
.summary-text, .summary-stats { | |
text-align: center; | |
color: #6c757d; | |
margin-bottom: 0.5rem; | |
} | |
} | |
.empty-state { | |
text-align: center; | |
padding: 3rem; | |
background-color: white; | |
border-radius: 0.25rem; | |
p { | |
color: #6c757d; | |
} | |
} | |
`] | |
}) | |
export class TestComponent { | |
private apiService = inject(ApiService); | |
running = false; | |
allSelected = false; | |
categories = { | |
ui: false, | |
backend: false, | |
integration: false, | |
spark: false | |
}; | |
selectedTests: string[] = []; | |
currentRun: TestRun | null = null; | |
toggleAll() { | |
this.categories.ui = this.allSelected; | |
this.categories.backend = this.allSelected; | |
this.categories.integration = this.allSelected; | |
this.categories.spark = this.allSelected; | |
this.updateSelection(); | |
} | |
updateSelection() { | |
this.selectedTests = []; | |
if (this.categories.ui) this.selectedTests.push('ui'); | |
if (this.categories.backend) this.selectedTests.push('backend'); | |
if (this.categories.integration) this.selectedTests.push('integration'); | |
if (this.categories.spark) this.selectedTests.push('spark'); | |
this.allSelected = this.selectedTests.length === 4; | |
} | |
runAllTests() { | |
this.running = true; | |
this.apiService.runTests('all').subscribe({ | |
next: (result) => { | |
this.currentRun = result; | |
this.running = false; | |
}, | |
error: (err) => { | |
console.error('Test run failed:', err); | |
this.running = false; | |
} | |
}); | |
} | |
runSelectedTests() { | |
// TODO: Implement selected tests | |
console.log('Running selected tests:', this.selectedTests); | |
this.runAllTests(); // For now, just run all | |
} | |
stopTests() { | |
this.running = false; | |
// TODO: Implement stop functionality | |
} | |
} |