#!/usr/bin/env node import { TemplateTransformer } from '../../cli/processors/template/template-transformer'; import { ClassDecoratorProcessor } from '../../cli/processors/class-decorator-processor'; import { DIProcessor } from '../../cli/processors/di-processor'; import { SignalTransformerProcessor } from '../../cli/processors/signal-transformer-processor'; interface TestResult { name: string; passed: boolean; error?: string; } const results: TestResult[] = []; function test(name: string, fn: () => void | Promise): void { try { const result = fn(); if (result instanceof Promise) { result .then(() => results.push({ name, passed: true })) .catch((e) => results.push({ name, passed: false, error: String(e) })); } else { results.push({ name, passed: true }); } } catch (e) { results.push({ name, passed: false, error: String(e) }); } } function assertEqual(actual: string, expected: string, message?: string): void { if (actual !== expected) { throw new Error( `${message || 'Assertion failed'}\nExpected:\n${expected}\nActual:\n${actual}`, ); } } function assertContains(actual: string, expected: string, message?: string): void { if (!actual.includes(expected)) { throw new Error( `${message || 'Assertion failed'}\nExpected to contain:\n${expected}\nActual:\n${actual}`, ); } } // ============================================================================ // TemplateTransformer Tests // ============================================================================ console.log('\nšŸ“¦ TemplateTransformer Tests\n'); const templateTransformer = new TemplateTransformer(); test('interpolation: {{ signal() }} -> [innerText] span', () => { const input = '
{{ userName() }}
'; const output = templateTransformer.transformInterpolation(input); assertContains(output, '[innerText]="userName()"'); }); test('interpolation: multiple interpolations', () => { const input = '
{{ a() }} and {{ b() }}
'; const output = templateTransformer.transformInterpolation(input); assertContains(output, '[innerText]="a()"'); assertContains(output, '[innerText]="b()"'); }); test('@if: simple condition', () => { const input = '@if (isVisible) {
Content
}'; const output = templateTransformer.transformControlFlowIf(input); assertContains(output, '*ngIf="isVisible"'); assertContains(output, '
Content
'); }); test('@if/@else: condition with else', () => { const input = '@if (isLoggedIn) { Welcome } @else { Login }'; const output = templateTransformer.transformControlFlowIf(input); assertContains(output, '*ngIf="isLoggedIn"'); assertContains(output, 'Welcome'); assertContains(output, 'Login'); }); test('@if/@else if/@else: multiple conditions', () => { const input = '@if (a) { A } @else if (b) { B } @else { C }'; const output = templateTransformer.transformControlFlowIf(input); assertContains(output, '*ngIf="a"'); assertContains(output, 'A'); assertContains(output, '*ngIf="!(a) && b"'); assertContains(output, 'B'); assertContains(output, '*ngIf="!(a) && !(b)"'); assertContains(output, 'C'); }); test('@for: simple loop', () => { const input = '@for (item of items) {
{{ item }}
}'; const output = templateTransformer.transformControlFlowFor(input); assertContains(output, '*ngFor="let item of items"'); }); test('@for: loop with track', () => { const input = '@for (item of items; track item.id) {
{{ item.name }}
}'; const output = templateTransformer.transformControlFlowFor(input); assertContains(output, '*ngFor="let item of items; trackBy: item.id"'); }); test('*ngIf: directive kept as is', () => { const input = '
Content
'; const output = templateTransformer.transformNgIfDirective(input); assertEqual(output, input); }); test('*ngFor: directive kept as is', () => { const input = '
{{ item }}
'; const output = templateTransformer.transformNgForDirective(input); assertEqual(output, input); }); test('input binding: kept as is', () => { const input = ''; const output = templateTransformer.transformInputBindings(input); assertEqual(output, input); }); test('output binding: kept as is', () => { const input = ''; const output = templateTransformer.transformOutputBindings(input); assertEqual(output, input); }); test('two-way binding: kept as is', () => { const input = ''; const output = templateTransformer.transformTwoWayBindings(input); assertEqual(output, input); }); test('transformAll: combined transformations', () => { const input = ` @if (isVisible) {
{{ message() }}
} `; const output = templateTransformer.transformAll(input); assertContains(output, '*ngIf="isVisible"'); assertContains(output, '[class]="myClass"'); assertContains(output, '(click)="handleClick()"'); assertContains(output, '[innerText]="message()"'); }); // ============================================================================ // ClassDecoratorProcessor Tests // ============================================================================ console.log('\nšŸ“¦ ClassDecoratorProcessor Tests\n'); const decoratorProcessor = new ClassDecoratorProcessor(); test('Component decorator: transforms to static property', async () => { const input = ` @Component({ selector: 'app-test', template: '
Test
', }) export class TestComponent {} `; const result = await decoratorProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'static _quarcComponent'); assertContains(result.source, "selector: 'app-test'"); assertContains(result.source, 'static _scopeId'); }); test('Injectable decorator: transforms to static property', async () => { const input = ` @Injectable() export class TestService {} `; const result = await decoratorProcessor.process({ filePath: '/test/test.service.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'static _quarcInjectable'); }); test('Directive decorator: transforms to static property', async () => { const input = ` @Directive({ selector: '[appHighlight]', }) export class HighlightDirective {} `; const result = await decoratorProcessor.process({ filePath: '/test/highlight.directive.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'static _quarcDirective'); }); test('Pipe decorator: transforms to static property', async () => { const input = ` @Pipe({ name: 'myPipe', }) export class MyPipe {} `; const result = await decoratorProcessor.process({ filePath: '/test/my.pipe.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'static _quarcPipe'); }); // ============================================================================ // DIProcessor Tests // ============================================================================ console.log('\nšŸ“¦ DIProcessor Tests\n'); const diProcessor = new DIProcessor(); test('DI: extracts constructor params', async () => { const input = ` export class TestComponent { constructor(private userService: UserService, private http: HttpClient) {} } `; const result = await diProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, "static __di_params__ = ['UserService', 'HttpClient']"); }); test('DI: includes HTMLElement param', async () => { const input = ` export class TestComponent { constructor(public _nativeElement: HTMLElement, private service: MyService) {} } `; const result = await diProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, "static __di_params__ = ['HTMLElement', 'MyService']"); }); test('DI: no params - no modification', async () => { const input = ` export class TestComponent { constructor() {} } `; const result = await diProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); if (result.modified !== false) { throw new Error('Expected modified to be false'); } }); test('DI: class with extends', async () => { const input = ` export class ChildComponent extends BaseComponent { constructor(private service: MyService) { super(); } } `; const result = await diProcessor.process({ filePath: '/test/child.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, "static __di_params__ = ['MyService']"); }); // ============================================================================ // SignalTransformerProcessor Tests // ============================================================================ console.log('\nšŸ“¦ SignalTransformerProcessor Tests\n'); const signalProcessor = new SignalTransformerProcessor(); test('Signal: transforms input()', async () => { const input = ` export class TestComponent { data = input(); } `; const result = await signalProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'input("data", this)'); }); test('Signal: transforms output()', async () => { const input = ` export class TestComponent { clicked = output(); } `; const result = await signalProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'output("clicked", this)'); }); test('Signal: transforms input with default value', async () => { const input = ` export class TestComponent { count = input(0); } `; const result = await signalProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'input("count", this, 0)'); }); test('Signal: adds _nativeElement to constructor', async () => { const input = ` export class TestComponent { data = input(); } `; const result = await signalProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'constructor(public _nativeElement: HTMLElement)'); }); test('Signal: preserves existing constructor params', async () => { const input = ` export class TestComponent { data = input(); constructor(private service: MyService) {} } `; const result = await signalProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); assertContains(result.source, 'constructor(public _nativeElement: HTMLElement, private service: MyService)'); }); test('Signal: does not duplicate _nativeElement', async () => { const input = ` export class TestComponent { data = input(); constructor(public _nativeElement: HTMLElement) {} } `; const result = await signalProcessor.process({ filePath: '/test/test.component.ts', fileDir: '/test', source: input, }); const nativeElementCount = (result.source.match(/_nativeElement/g) || []).length; if (nativeElementCount > 2) { throw new Error(`_nativeElement appears ${nativeElementCount} times, expected max 2`); } }); // ============================================================================ // Run all tests // ============================================================================ async function runTests() { await new Promise(resolve => setTimeout(resolve, 100)); console.log('\n' + '='.repeat(60)); console.log('šŸ“Š TEST RESULTS'); console.log('='.repeat(60)); let passed = 0; let failed = 0; for (const result of results) { if (result.passed) { console.log(`āœ… ${result.name}`); passed++; } else { console.log(`āŒ ${result.name}`); console.log(` Error: ${result.error}`); failed++; } } console.log('\n' + '-'.repeat(60)); console.log(`Total: ${results.length} | Passed: ${passed} | Failed: ${failed}`); console.log('-'.repeat(60)); if (failed > 0) { process.exit(1); } } runTests();