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,008 changes: 823 additions & 185 deletions online-soroban-compiler/apps/frontend/package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -74,50 +74,25 @@ <h2 class="text-xl sm:text-2xl font-bold text-white tracking-tight">Soroban Smar
</div>

<!-- Output Panel -->
<div *ngIf="errorMessage || outputMessage" class="border-t border-gray-700/50 animate-slide-down">
<div [ngClass]="{
'bg-red-900/20 border-l-red-500 text-red-100': outputType === 'error',
'bg-green-900/20 border-l-green-500 text-green-100': outputType === 'success',
'bg-blue-900/20 border-l-blue-500 text-blue-100': outputType === 'info'
}" class="p-4 border-l-4 bg-gray-800/30 backdrop-blur-sm">
<div class="flex justify-between items-start gap-4">
<div class="flex items-start gap-3 flex-1">
<!-- Icon -->
<div class="flex-shrink-0 mt-0.5">
<svg *ngIf="outputType === 'error'" class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
<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"/>
</svg>
<svg *ngIf="outputType === 'success'" class="h-5 w-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
<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"/>
</svg>
<svg *ngIf="outputType === 'info'" class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
<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"/>
</svg>
</div>

<!-- Message -->
<div class="flex-1">
<p class="text-sm font-medium" [ngClass]="{
'text-red-200': outputType === 'error',
'text-green-200': outputType === 'success',
'text-blue-200': outputType === 'info'
}">
{{ errorMessage || outputMessage }}
</p>
</div>
</div>

<!-- Close button -->
<button
(click)="clearOutput()"
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"
aria-label="Clear output message"
>
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<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"/>
</svg>
</button>
<div *ngIf="outputText" class="border-t border-gray-700/50 animate-slide-down p-4">
<div class="flex justify-between items-start gap-4">
<div class="flex-1">
<app-output
[outputText]="outputText"
[outputType]="outputType">
</app-output>
</div>

<!-- Close button -->
<button
(click)="clearOutput()"
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"
aria-label="Clear output message"
>
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<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"/>
</svg>
</button>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,10 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { MonacoEditorModule, MonacoEditorLoaderService } from '@materia-ui/ngx-monaco-editor';
import { of } from 'rxjs';
import { provideZoneChangeDetection } from '@angular/core';

import { EditorComponent } from './editor.component';

describe('EditorComponent', () => {
let component: EditorComponent;
let fixture: ComponentFixture<EditorComponent>;
let mockMonacoLoaderService: jasmine.SpyObj<MonacoEditorLoaderService>;

beforeEach(async () => {
// Create mock Monaco loader service
mockMonacoLoaderService = jasmine.createSpyObj('MonacoEditorLoaderService', [], {
isMonacoLoaded$: of(true)
});

await TestBed.configureTestingModule({
imports: [EditorComponent, FormsModule, MonacoEditorModule],
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{ provide: MonacoEditorLoaderService, useValue: mockMonacoLoaderService }
]
})
.compileComponents();

fixture = TestBed.createComponent(EditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
beforeEach(() => {
component = new EditorComponent();
});

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

it('should set loading state when compile is called', () => {
component.onCompile();
expect(component.isLoading).toBe(true);
});

it('should set loading state when test is called', () => {
component.onTest();
expect(component.isLoading).toBe(true);
});

it('should have proper editor options types', () => {
it('should have proper editor options', () => {
expect(component.editorOptions.theme).toBe('vs-dark');
expect(component.editorOptions.language).toBe('rust');
expect(component.editorOptions.automaticLayout).toBe(true);
expect(component.editorOptions.readOnly).toBe(false);
});

it('should provide getCurrentCode method', () => {
const currentCode = component.getCurrentCode();
expect(typeof currentCode).toBe('string');
expect(currentCode).toContain('Soroban Smart Contract');
});
it('should have outputText getter', () => {
component.outputMessage = 'Test output';
expect(component.outputText).toBe('Test output');

it('should provide setEditorCode method', () => {
const newCode = 'fn test() {}';
component.setEditorCode(newCode);
expect(component.code).toBe(newCode);
component.errorMessage = 'Test error';
component.outputMessage = '';
expect(component.outputText).toBe('Test error');
});

it('should handle editor initialization', () => {
const mockEditor = {
getValue: jasmine.createSpy('getValue').and.returnValue('test code'),
setValue: jasmine.createSpy('setValue'),
focus: jasmine.createSpy('focus'),
layout: jasmine.createSpy('layout'),
dispose: jasmine.createSpy('dispose')
};
it('should clear output correctly', () => {
component.errorMessage = 'Some error';
component.outputMessage = 'Some output';
component.clearOutput();

component.onEditorInit(mockEditor);
expect(component.editorLoaded).toBe(true);
expect(component.errorMessage).toBe('');
expect(component.outputMessage).toBe('');
expect(component.outputType).toBe('info');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FormsModule } from '@angular/forms';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { PLATFORM_ID, inject } from '@angular/core';
import { CompilerService } from '../../services/compiler';
import { OutputComponent, OutputType } from '../output/output.component';


const DEFAULT_RUST_CODE = `// Welcome to Soroban Smart Contract Editor
Expand All @@ -25,7 +26,7 @@ impl HelloContract {
@Component({
selector: 'app-editor',
standalone: true,
imports: [CommonModule, FormsModule, MonacoEditorModule],
imports: [CommonModule, FormsModule, MonacoEditorModule, OutputComponent],
templateUrl: './editor.component.html',
styleUrl: './editor.component.css'
})
Expand All @@ -39,7 +40,7 @@ export class EditorComponent implements OnDestroy {
// Validation and output properties
errorMessage: string = '';
outputMessage: string = '';
outputType: 'error' | 'success' | 'info' = 'info';
outputType: OutputType = 'info';

editorOptions = {
theme: 'vs-dark',
Expand All @@ -52,6 +53,11 @@ export class EditorComponent implements OnDestroy {
lineNumbers: 'on' as const
};

// Computed property for output text
get outputText(): string {
return this.errorMessage || this.outputMessage;
}

ngOnDestroy(): void {
this.timeoutIds.forEach(id => clearTimeout(id));
this.timeoutIds.clear();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.output-container {
position: relative;
transition: all 0.2s ease-in-out;
}

.output-container:hover {
@apply shadow-sm border-gray-400;
}

/* Ensure proper scrolling for long outputs */
.output-container pre {
overflow-wrap: break-word;
word-break: break-word;
}

/* Loading state indicator if needed */
.output-container.loading {
@apply opacity-75;
}

/* Accessibility improvements */
.output-container:focus-within {
@apply ring-2 ring-blue-500 ring-opacity-50;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="output-container bg-gray-100 border border-gray-300 rounded-lg p-4 max-h-96 overflow-y-auto">
<pre class="whitespace-pre-wrap text-sm font-mono leading-relaxed"
[class.text-red-600]="outputType === 'error'"
[class.text-green-600]="outputType === 'success'"
[class.text-blue-600]="outputType === 'info'"
[attr.aria-label]="'Output display: ' + outputType">{{ outputText || 'No output to display' }}</pre>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { OutputComponent } from './output.component';

describe('OutputComponent', () => {
let component: OutputComponent;

beforeEach(() => {
component = new OutputComponent();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should initialize with empty output text', () => {
expect(component.outputText).toBe('');
});

it('should initialize with info output type', () => {
expect(component.outputType).toBe('info');
});

it('should accept input values', () => {
component.outputText = 'Test output message';
component.outputType = 'error';

expect(component.outputText).toBe('Test output message');
expect(component.outputType).toBe('error');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';

export type OutputType = 'error' | 'success' | 'info';

@Component({
selector: 'app-output',
standalone: true,
imports: [CommonModule],
templateUrl: './output.component.html',
styleUrl: './output.component.css'
})
export class OutputComponent {
@Input() outputText: string = '';
@Input() outputType: OutputType = 'info';
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';

import { Compiler } from './compiler';
import { CompilerService } from './compiler';

describe('Compiler', () => {
let service: Compiler;
describe('CompilerService', () => {
let service: CompilerService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(Compiler);
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(CompilerService);
});

it('should be created', () => {
Expand Down
Loading