Skip to content

Commit fb3bad1

Browse files
authored
Merge pull request #74 from pushkarm029/output
feat: enhance editor component with output handling
2 parents 92c408a + e90f7fc commit fb3bad1

9 files changed

Lines changed: 949 additions & 292 deletions

File tree

online-soroban-compiler/apps/frontend/package-lock.json

Lines changed: 823 additions & 185 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

online-soroban-compiler/apps/frontend/src/app/components/editor/editor.component.html

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -74,50 +74,25 @@ <h2 class="text-xl sm:text-2xl font-bold text-white tracking-tight">Soroban Smar
7474
</div>
7575

7676
<!-- Output Panel -->
77-
<div *ngIf="errorMessage || outputMessage" class="border-t border-gray-700/50 animate-slide-down">
78-
<div [ngClass]="{
79-
'bg-red-900/20 border-l-red-500 text-red-100': outputType === 'error',
80-
'bg-green-900/20 border-l-green-500 text-green-100': outputType === 'success',
81-
'bg-blue-900/20 border-l-blue-500 text-blue-100': outputType === 'info'
82-
}" class="p-4 border-l-4 bg-gray-800/30 backdrop-blur-sm">
83-
<div class="flex justify-between items-start gap-4">
84-
<div class="flex items-start gap-3 flex-1">
85-
<!-- Icon -->
86-
<div class="flex-shrink-0 mt-0.5">
87-
<svg *ngIf="outputType === 'error'" class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
88-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
89-
</svg>
90-
<svg *ngIf="outputType === 'success'" class="h-5 w-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
91-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
92-
</svg>
93-
<svg *ngIf="outputType === 'info'" class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
94-
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
95-
</svg>
96-
</div>
97-
98-
<!-- Message -->
99-
<div class="flex-1">
100-
<p class="text-sm font-medium" [ngClass]="{
101-
'text-red-200': outputType === 'error',
102-
'text-green-200': outputType === 'success',
103-
'text-blue-200': outputType === 'info'
104-
}">
105-
{{ errorMessage || outputMessage }}
106-
</p>
107-
</div>
108-
</div>
109-
110-
<!-- Close button -->
111-
<button
112-
(click)="clearOutput()"
113-
class="flex-shrink-0 p-1 rounded-md text-gray-400 hover:text-white hover:bg-gray-700/50 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-500"
114-
aria-label="Clear output message"
115-
>
116-
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
117-
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
118-
</svg>
119-
</button>
77+
<div *ngIf="outputText" class="border-t border-gray-700/50 animate-slide-down p-4">
78+
<div class="flex justify-between items-start gap-4">
79+
<div class="flex-1">
80+
<app-output
81+
[outputText]="outputText"
82+
[outputType]="outputType">
83+
</app-output>
12084
</div>
85+
86+
<!-- Close button -->
87+
<button
88+
(click)="clearOutput()"
89+
class="flex-shrink-0 p-1 rounded-md text-gray-400 hover:text-white hover:bg-gray-700/50 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-500"
90+
aria-label="Clear output message"
91+
>
92+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
93+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
94+
</svg>
95+
</button>
12196
</div>
12297
</div>
12398

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,10 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing';
2-
import { FormsModule } from '@angular/forms';
3-
import { MonacoEditorModule, MonacoEditorLoaderService } from '@materia-ui/ngx-monaco-editor';
4-
import { of } from 'rxjs';
5-
import { provideZoneChangeDetection } from '@angular/core';
6-
71
import { EditorComponent } from './editor.component';
82

93
describe('EditorComponent', () => {
104
let component: EditorComponent;
11-
let fixture: ComponentFixture<EditorComponent>;
12-
let mockMonacoLoaderService: jasmine.SpyObj<MonacoEditorLoaderService>;
13-
14-
beforeEach(async () => {
15-
// Create mock Monaco loader service
16-
mockMonacoLoaderService = jasmine.createSpyObj('MonacoEditorLoaderService', [], {
17-
isMonacoLoaded$: of(true)
18-
});
195

20-
await TestBed.configureTestingModule({
21-
imports: [EditorComponent, FormsModule, MonacoEditorModule],
22-
providers: [
23-
provideZoneChangeDetection({ eventCoalescing: true }),
24-
{ provide: MonacoEditorLoaderService, useValue: mockMonacoLoaderService }
25-
]
26-
})
27-
.compileComponents();
28-
29-
fixture = TestBed.createComponent(EditorComponent);
30-
component = fixture.componentInstance;
31-
fixture.detectChanges();
6+
beforeEach(() => {
7+
component = new EditorComponent();
328
});
339

3410
it('should create', () => {
@@ -51,45 +27,28 @@ describe('EditorComponent', () => {
5127
expect(component.isLoading).toBe(false);
5228
});
5329

54-
it('should set loading state when compile is called', () => {
55-
component.onCompile();
56-
expect(component.isLoading).toBe(true);
57-
});
58-
59-
it('should set loading state when test is called', () => {
60-
component.onTest();
61-
expect(component.isLoading).toBe(true);
62-
});
63-
64-
it('should have proper editor options types', () => {
30+
it('should have proper editor options', () => {
6531
expect(component.editorOptions.theme).toBe('vs-dark');
6632
expect(component.editorOptions.language).toBe('rust');
6733
expect(component.editorOptions.automaticLayout).toBe(true);
68-
expect(component.editorOptions.readOnly).toBe(false);
6934
});
7035

71-
it('should provide getCurrentCode method', () => {
72-
const currentCode = component.getCurrentCode();
73-
expect(typeof currentCode).toBe('string');
74-
expect(currentCode).toContain('Soroban Smart Contract');
75-
});
36+
it('should have outputText getter', () => {
37+
component.outputMessage = 'Test output';
38+
expect(component.outputText).toBe('Test output');
7639

77-
it('should provide setEditorCode method', () => {
78-
const newCode = 'fn test() {}';
79-
component.setEditorCode(newCode);
80-
expect(component.code).toBe(newCode);
40+
component.errorMessage = 'Test error';
41+
component.outputMessage = '';
42+
expect(component.outputText).toBe('Test error');
8143
});
8244

83-
it('should handle editor initialization', () => {
84-
const mockEditor = {
85-
getValue: jasmine.createSpy('getValue').and.returnValue('test code'),
86-
setValue: jasmine.createSpy('setValue'),
87-
focus: jasmine.createSpy('focus'),
88-
layout: jasmine.createSpy('layout'),
89-
dispose: jasmine.createSpy('dispose')
90-
};
45+
it('should clear output correctly', () => {
46+
component.errorMessage = 'Some error';
47+
component.outputMessage = 'Some output';
48+
component.clearOutput();
9149

92-
component.onEditorInit(mockEditor);
93-
expect(component.editorLoaded).toBe(true);
50+
expect(component.errorMessage).toBe('');
51+
expect(component.outputMessage).toBe('');
52+
expect(component.outputType).toBe('info');
9453
});
9554
});

online-soroban-compiler/apps/frontend/src/app/components/editor/editor.component.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FormsModule } from '@angular/forms';
44
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
55
import { PLATFORM_ID, inject } from '@angular/core';
66
import { CompilerService } from '../../services/compiler';
7+
import { OutputComponent, OutputType } from '../output/output.component';
78

89

910
const DEFAULT_RUST_CODE = `// Welcome to Soroban Smart Contract Editor
@@ -25,7 +26,7 @@ impl HelloContract {
2526
@Component({
2627
selector: 'app-editor',
2728
standalone: true,
28-
imports: [CommonModule, FormsModule, MonacoEditorModule],
29+
imports: [CommonModule, FormsModule, MonacoEditorModule, OutputComponent],
2930
templateUrl: './editor.component.html',
3031
styleUrl: './editor.component.css'
3132
})
@@ -39,7 +40,7 @@ export class EditorComponent implements OnDestroy {
3940
// Validation and output properties
4041
errorMessage: string = '';
4142
outputMessage: string = '';
42-
outputType: 'error' | 'success' | 'info' = 'info';
43+
outputType: OutputType = 'info';
4344

4445
editorOptions = {
4546
theme: 'vs-dark',
@@ -52,6 +53,11 @@ export class EditorComponent implements OnDestroy {
5253
lineNumbers: 'on' as const
5354
};
5455

56+
// Computed property for output text
57+
get outputText(): string {
58+
return this.errorMessage || this.outputMessage;
59+
}
60+
5561
ngOnDestroy(): void {
5662
this.timeoutIds.forEach(id => clearTimeout(id));
5763
this.timeoutIds.clear();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.output-container {
2+
position: relative;
3+
transition: all 0.2s ease-in-out;
4+
}
5+
6+
.output-container:hover {
7+
@apply shadow-sm border-gray-400;
8+
}
9+
10+
/* Ensure proper scrolling for long outputs */
11+
.output-container pre {
12+
overflow-wrap: break-word;
13+
word-break: break-word;
14+
}
15+
16+
/* Loading state indicator if needed */
17+
.output-container.loading {
18+
@apply opacity-75;
19+
}
20+
21+
/* Accessibility improvements */
22+
.output-container:focus-within {
23+
@apply ring-2 ring-blue-500 ring-opacity-50;
24+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div class="output-container bg-gray-100 border border-gray-300 rounded-lg p-4 max-h-96 overflow-y-auto">
2+
<pre class="whitespace-pre-wrap text-sm font-mono leading-relaxed"
3+
[class.text-red-600]="outputType === 'error'"
4+
[class.text-green-600]="outputType === 'success'"
5+
[class.text-blue-600]="outputType === 'info'"
6+
[attr.aria-label]="'Output display: ' + outputType">{{ outputText || 'No output to display' }}</pre>
7+
</div>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { OutputComponent } from './output.component';
2+
3+
describe('OutputComponent', () => {
4+
let component: OutputComponent;
5+
6+
beforeEach(() => {
7+
component = new OutputComponent();
8+
});
9+
10+
it('should create', () => {
11+
expect(component).toBeTruthy();
12+
});
13+
14+
it('should initialize with empty output text', () => {
15+
expect(component.outputText).toBe('');
16+
});
17+
18+
it('should initialize with info output type', () => {
19+
expect(component.outputType).toBe('info');
20+
});
21+
22+
it('should accept input values', () => {
23+
component.outputText = 'Test output message';
24+
component.outputType = 'error';
25+
26+
expect(component.outputText).toBe('Test output message');
27+
expect(component.outputType).toBe('error');
28+
});
29+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Component, Input } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
4+
export type OutputType = 'error' | 'success' | 'info';
5+
6+
@Component({
7+
selector: 'app-output',
8+
standalone: true,
9+
imports: [CommonModule],
10+
templateUrl: './output.component.html',
11+
styleUrl: './output.component.css'
12+
})
13+
export class OutputComponent {
14+
@Input() outputText: string = '';
15+
@Input() outputType: OutputType = 'info';
16+
}

online-soroban-compiler/apps/frontend/src/app/services/compiler.spec.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { TestBed } from '@angular/core/testing';
2+
import { HttpClientTestingModule } from '@angular/common/http/testing';
23

3-
import { Compiler } from './compiler';
4+
import { CompilerService } from './compiler';
45

5-
describe('Compiler', () => {
6-
let service: Compiler;
6+
describe('CompilerService', () => {
7+
let service: CompilerService;
78

89
beforeEach(() => {
9-
TestBed.configureTestingModule({});
10-
service = TestBed.inject(Compiler);
10+
TestBed.configureTestingModule({
11+
imports: [HttpClientTestingModule]
12+
});
13+
service = TestBed.inject(CompilerService);
1114
});
1215

1316
it('should be created', () => {

0 commit comments

Comments
 (0)