From ba47312f55c2aa34079edafd55f56496fa48c313 Mon Sep 17 00:00:00 2001 From: Michal Date: Sun, 18 Jan 2026 21:29:07 +0100 Subject: [PATCH] pipes --- PIPE_IMPLEMENTATION_FIX.md | 169 ++++++++++++++++++ .../directive-collector-processor.ts | 52 +++++- .../template/template-transformer.ts | 66 ++++++- core/angular/pipe.ts | 8 + core/index.ts | 3 +- core/module/pipe-registry.ts | 46 +++++ core/module/type.ts | 1 + core/module/web-component.ts | 22 +++ tests/unit/test-devices-component.html | 60 +++++++ tests/unit/test-devices-component.ts | 161 +++++++++++++++++ tests/unit/test-for-transformation.ts | 100 +++++++++++ .../unit/test-interpolation-transformation.ts | 133 ++++++++++++++ .../unit/test-pipe-with-logical-operators.ts | 88 +++++++++ 13 files changed, 898 insertions(+), 11 deletions(-) create mode 100644 PIPE_IMPLEMENTATION_FIX.md create mode 100644 core/module/pipe-registry.ts create mode 100644 tests/unit/test-devices-component.html create mode 100644 tests/unit/test-devices-component.ts create mode 100644 tests/unit/test-for-transformation.ts create mode 100644 tests/unit/test-interpolation-transformation.ts create mode 100644 tests/unit/test-pipe-with-logical-operators.ts diff --git a/PIPE_IMPLEMENTATION_FIX.md b/PIPE_IMPLEMENTATION_FIX.md new file mode 100644 index 0000000..a28ddbf --- /dev/null +++ b/PIPE_IMPLEMENTATION_FIX.md @@ -0,0 +1,169 @@ +# Naprawa implementacji Pipe - Problem z operatorem || + +## Problem + +Po dodaniu obsługi pipes do frameworka Quarc, komponent devices w `/web/IoT/Ant` przestał renderować zawartość. + +### Przyczyna + +Implementacja `transformPipeExpression` w `template-transformer.ts` błędnie traktowała operator logiczny `||` jako separator pipe `|`. + +Wyrażenie: +```typescript +{{ device.name || 'Unnamed' }} +``` + +Było transformowane na: +```typescript +this._pipes?.['']?.transform(this._pipes?.['Unnamed']?.transform(device.name)) +``` + +Zamiast pozostać jako: +```typescript +device.name || 'Unnamed' +``` + +## Rozwiązanie + +Dodano metodę `splitByPipe()` która poprawnie rozróżnia: +- Pojedynczy `|` - separator pipe +- Podwójny `||` - operator logiczny OR +- Podwójny `&&` - operator logiczny AND + +### Zmieniony plik + +**`/web/quarc/cli/processors/template/template-transformer.ts`** + +```typescript +private transformPipeExpression(expression: string): string { + const parts = this.splitByPipe(expression); + + if (parts.length === 1) { + return expression; + } + + let result = parts[0].trim(); + + for (let i = 1; i < parts.length; i++) { + const pipePart = parts[i].trim(); + const colonIndex = pipePart.indexOf(':'); + + if (colonIndex === -1) { + const pipeName = pipePart.trim(); + result = `this._pipes?.['${pipeName}']?.transform(${result})`; + } else { + const pipeName = pipePart.substring(0, colonIndex).trim(); + const argsStr = pipePart.substring(colonIndex + 1).trim(); + const args = argsStr.split(':').map(arg => arg.trim()); + const argsJoined = args.join(', '); + result = `this._pipes?.['${pipeName}']?.transform(${result}, ${argsJoined})`; + } + } + + return result; +} + +private splitByPipe(expression: string): string[] { + const parts: string[] = []; + let current = ''; + let i = 0; + + while (i < expression.length) { + const char = expression[i]; + + if (char === '|') { + if (i + 1 < expression.length && expression[i + 1] === '|') { + // To jest || (operator logiczny), nie pipe + current += '||'; + i += 2; + } else { + // To jest | (separator pipe) + parts.push(current); + current = ''; + i++; + } + } else { + current += char; + i++; + } + } + + if (current) { + parts.push(current); + } + + return parts.length > 0 ? parts : [expression]; +} +``` + +## Testy + +Utworzono testy w `/web/quarc/tests/unit/`: + +1. **test-for-transformation.ts** - Weryfikuje transformację `@for` do `*ngFor` +2. **test-interpolation-transformation.ts** - Weryfikuje transformację interpolacji `{{ }}` +3. **test-pipe-with-logical-operators.ts** - Weryfikuje rozróżnienie pipe `|` od operatorów `||` i `&&` + +### Wyniki testów + +``` +✅ @FOR TRANSFORMATION TEST PASSED (6/6) +✅ INTERPOLATION TRANSFORMATION TEST PASSED (8/8) +✅ PIPE VS LOGICAL OPERATORS TEST PASSED (7/7) +``` + +## Przykłady działania + +### Operator || (poprawnie zachowany) +```typescript +// Input +{{ device.name || 'Unnamed' }} + +// Output + +``` + +### Prawdziwy pipe (poprawnie transformowany) +```typescript +// Input +{{ value | uppercase }} + +// Output + +``` + +### Kombinacja || i pipe +```typescript +// Input +{{ (value || 'default') | uppercase }} + +// Output + +``` + +### Łańcuch pipes +```typescript +// Input +{{ value | lowercase | slice:0:5 }} + +// Output + +``` + +## Weryfikacja + +Build aplikacji IoT/Ant przechodzi pomyślnie: +```bash +cd /web/IoT/Ant/assets/resources/quarc +npm run build +# ✅ Build completed | Environment: production +``` + +## Wpływ na istniejący kod + +Naprawa jest **wstecznie kompatybilna** - nie zmienia zachowania dla: +- Prostych interpolacji bez operatorów logicznych +- Istniejących pipes +- Transformacji `@for` i `@if` + +Naprawia tylko błędną interpretację operatorów logicznych jako separatorów pipe. diff --git a/cli/processors/directive-collector-processor.ts b/cli/processors/directive-collector-processor.ts index 1d26609..a474cb0 100644 --- a/cli/processors/directive-collector-processor.ts +++ b/cli/processors/directive-collector-processor.ts @@ -40,18 +40,24 @@ export class DirectiveCollectorProcessor extends BaseProcessor { } const importsContent = importsMatch[1]; - const importNames = this.parseImportNames(importsContent); + const { directives, pipes } = this.categorizeImports(importsContent, source); - if (importNames.length === 0) { - continue; + let insert = ''; + + if (directives.length > 0) { + insert += `\n static _quarcDirectives = [${directives.join(', ')}];`; } - const directivesProperty = `\n static _quarcDirectives = [${importNames.join(', ')}];`; + if (pipes.length > 0) { + insert += `\n static _quarcPipes = [${pipes.join(', ')}];`; + } - replacements.push({ - position: scopeIdEnd, - insert: directivesProperty, - }); + if (insert) { + replacements.push({ + position: scopeIdEnd, + insert, + }); + } } for (let i = replacements.length - 1; i >= 0; i--) { @@ -63,6 +69,36 @@ export class DirectiveCollectorProcessor extends BaseProcessor { return modified ? this.changed(source) : this.noChange(source); } + private categorizeImports(importsContent: string, source: string): { directives: string[]; pipes: string[] } { + const importNames = this.parseImportNames(importsContent); + const directives: string[] = []; + const pipes: string[] = []; + + for (const name of importNames) { + if (this.isPipe(name, source)) { + pipes.push(name); + } else { + directives.push(name); + } + } + + return { directives, pipes }; + } + + private isPipe(className: string, source: string): boolean { + const classPattern = new RegExp(`class\\s+${className}\\s*(?:extends|implements|\\{)`); + const classMatch = source.match(classPattern); + + if (!classMatch) { + return false; + } + + const beforeClass = source.substring(0, classMatch.index!); + const pipeDecoratorPattern = new RegExp(`static\\s+_quarcPipe\\s*=.*?${className}`, 's'); + + return pipeDecoratorPattern.test(source); + } + private parseImportNames(importsContent: string): string[] { return importsContent .split(',') diff --git a/cli/processors/template/template-transformer.ts b/cli/processors/template/template-transformer.ts index ad606f9..7ab3708 100644 --- a/cli/processors/template/template-transformer.ts +++ b/cli/processors/template/template-transformer.ts @@ -48,7 +48,8 @@ export class TemplateTransformer { parts.push(`'${literal}'`); } } - parts.push(`(${match[1].trim()})`); + const transformedExpr = this.transformPipeExpression(match[1].trim()); + parts.push(`(${transformedExpr})`); lastIndex = exprRegex.lastIndex; } @@ -80,10 +81,71 @@ export class TemplateTransformer { private transformContentInterpolation(content: string): string { return content.replace( /\{\{\s*([^}]+?)\s*\}\}/g, - (_, expr) => ``, + (_, expr) => { + const transformedExpr = this.transformPipeExpression(expr.trim()); + return ``; + }, ); } + private transformPipeExpression(expression: string): string { + const parts = this.splitByPipe(expression); + + if (parts.length === 1) { + return expression; + } + + let result = parts[0].trim(); + + for (let i = 1; i < parts.length; i++) { + const pipePart = parts[i].trim(); + const colonIndex = pipePart.indexOf(':'); + + if (colonIndex === -1) { + const pipeName = pipePart.trim(); + result = `this._pipes?.['${pipeName}']?.transform(${result})`; + } else { + const pipeName = pipePart.substring(0, colonIndex).trim(); + const argsStr = pipePart.substring(colonIndex + 1).trim(); + const args = argsStr.split(':').map(arg => arg.trim()); + const argsJoined = args.join(', '); + result = `this._pipes?.['${pipeName}']?.transform(${result}, ${argsJoined})`; + } + } + + return result; + } + + private splitByPipe(expression: string): string[] { + const parts: string[] = []; + let current = ''; + let i = 0; + + while (i < expression.length) { + const char = expression[i]; + + if (char === '|') { + if (i + 1 < expression.length && expression[i + 1] === '|') { + current += '||'; + i += 2; + } else { + parts.push(current); + current = ''; + i++; + } + } else { + current += char; + i++; + } + } + + if (current) { + parts.push(current); + } + + return parts.length > 0 ? parts : [expression]; + } + transformControlFlowIf(content: string): string { return this.controlFlowTransformer.transform(content); } diff --git a/core/angular/pipe.ts b/core/angular/pipe.ts index 72fcbe9..cd9326e 100644 --- a/core/angular/pipe.ts +++ b/core/angular/pipe.ts @@ -10,6 +10,14 @@ export interface PipeOptions { pure?: boolean; } +/** + * Interfejs dla pipe transformacji. + * Każdy pipe musi implementować metodę transform. + */ +export interface PipeTransform { + transform(value: any, ...args: any[]): any; +} + /** * Dekorator pipe. * diff --git a/core/index.ts b/core/index.ts index 9c70fbf..feb2c84 100644 --- a/core/index.ts +++ b/core/index.ts @@ -11,11 +11,12 @@ export { WebComponent } from "./module/web-component"; export { WebComponentFactory } from "./module/web-component-factory"; export { DirectiveRegistry } from "./module/directive-registry"; export { DirectiveRunner, DirectiveInstance } from "./module/directive-runner"; +export { PipeRegistry } from "./module/pipe-registry"; // Decorators export { Component, ComponentOptions } from "./angular/component"; export { Directive, DirectiveOptions, IDirective } from "./angular/directive"; -export { Pipe, PipeOptions } from "./angular/pipe"; +export { Pipe, PipeOptions, PipeTransform } from "./angular/pipe"; export { Injectable, InjectableOptions } from "./angular/injectable"; export { Input, input, createInput, createRequiredInput } from "./angular/input"; export type { InputSignal, InputOptions } from "./angular/input"; diff --git a/core/module/pipe-registry.ts b/core/module/pipe-registry.ts new file mode 100644 index 0000000..7e76e77 --- /dev/null +++ b/core/module/pipe-registry.ts @@ -0,0 +1,46 @@ +import { Type } from './type'; + +export interface PipeMetadata { + name: string; + pure: boolean; +} + +export class PipeRegistry { + private static instance: PipeRegistry; + private pipes = new Map>(); + private pipeMetadata = new Map, PipeMetadata>(); + + private constructor() {} + + static get(): PipeRegistry { + if (!PipeRegistry.instance) { + PipeRegistry.instance = new PipeRegistry(); + } + return PipeRegistry.instance; + } + + register(pipeType: Type): void { + const metadata = (pipeType as any)._quarcPipe?.[0]; + if (!metadata) { + return; + } + + const pipeName = metadata.name; + const pure = metadata.pure !== false; + + this.pipes.set(pipeName, pipeType); + this.pipeMetadata.set(pipeType, { name: pipeName, pure }); + } + + getPipe(name: string): Type | undefined { + return this.pipes.get(name); + } + + getPipeMetadata(pipeType: Type): PipeMetadata | undefined { + return this.pipeMetadata.get(pipeType); + } + + getAllPipes(): Map> { + return new Map(this.pipes); + } +} diff --git a/core/module/type.ts b/core/module/type.ts index 8c97fc1..74423d7 100644 --- a/core/module/type.ts +++ b/core/module/type.ts @@ -7,6 +7,7 @@ export interface Type { export interface ComponentType extends Type { _quarcComponent: [ComponentOptions]; _quarcDirectives?: DirectiveType[]; + _quarcPipes?: Type[]; _scopeId: string; } diff --git a/core/module/web-component.ts b/core/module/web-component.ts index b6c1a76..f6fc874 100644 --- a/core/module/web-component.ts +++ b/core/module/web-component.ts @@ -8,6 +8,7 @@ import { DirectiveInstance, effect, EffectRef, + PipeRegistry, } from '../index'; interface QuarcScopeRegistry { @@ -107,10 +108,31 @@ export class WebComponent extends HTMLElement { this.setAttribute(`_nghost-${this.runtimeScopeId}`, ''); } + this.initializePipes(); + this._initialized = true; this.renderComponent(); } + private initializePipes(): void { + if (!this.componentInstance || !this.componentType) return; + + const pipes = this.componentType._quarcPipes || []; + const pipeRegistry = PipeRegistry.get(); + const pipeInstances: Record = {}; + + for (const pipeType of pipes) { + pipeRegistry.register(pipeType); + const metadata = pipeRegistry.getPipeMetadata(pipeType); + if (metadata) { + const pipeInstance = new pipeType(); + pipeInstances[metadata.name] = pipeInstance; + } + } + + (this.componentInstance as any)._pipes = pipeInstances; + } + renderComponent(): void { if (!this.componentInstance || !this.componentType) return; diff --git a/tests/unit/test-devices-component.html b/tests/unit/test-devices-component.html new file mode 100644 index 0000000..2900eee --- /dev/null +++ b/tests/unit/test-devices-component.html @@ -0,0 +1,60 @@ + + + + + + Test: Devices Component + + + +

Test: Devices Component Rendering

+
+
+ + + + diff --git a/tests/unit/test-devices-component.ts b/tests/unit/test-devices-component.ts new file mode 100644 index 0000000..ea85fe7 --- /dev/null +++ b/tests/unit/test-devices-component.ts @@ -0,0 +1,161 @@ +import { Component, signal, OnInit } from "../../core/index"; +import { bootstrapApplication } from "../../platform-browser/browser"; + +// Symulacja IconComponent +@Component({ + selector: 'test-icon', + template: 'Icon', +}) +class TestIconComponent {} + +// Symulacja Device interface +interface Device { + address: string; + name: string; + offline: boolean; +} + +// Reprodukcja komponentu DevicesComponent z /web/IoT/Ant +@Component({ + selector: 'test-devices', + template: ` +
+ @for (device of devices(); track device.address) { +
+
+ +
+
+
{{ device.name || 'Unnamed' }}
+
{{ device.address }}
+
+
+ } +
+ + `, + imports: [TestIconComponent], +}) +class TestDevicesComponent implements OnInit { + public devices = signal([]); + public deviceCount = signal(0); + + ngOnInit(): void { + this.loadDevices(); + } + + private loadDevices(): void { + const mockDevices: Device[] = [ + { address: '192.168.1.1', name: 'Device 1', offline: false }, + { address: '192.168.1.2', name: 'Device 2', offline: false }, + { address: '192.168.1.3', name: 'Device 3', offline: true }, + ]; + + this.devices.set(mockDevices); + this.deviceCount.set(mockDevices.length); + } + + public openDevice(address: string): void { + console.log('Opening device:', address); + } +} + +// Root component +@Component({ + selector: 'test-app', + template: '', + imports: [TestDevicesComponent], +}) +class TestAppComponent {} + +// Test suite +export function runDevicesComponentTests() { + console.log('\n=== Test: Devices Component Rendering ===\n'); + + const container = document.createElement('div'); + container.id = 'test-container'; + document.body.appendChild(container); + + bootstrapApplication(TestAppComponent, { + providers: [], + }); + + setTimeout(() => { + const appElement = document.querySelector('test-app'); + console.log('App element:', appElement); + console.log('App element HTML:', appElement?.innerHTML); + + const devicesElement = document.querySelector('test-devices'); + console.log('\nDevices element:', devicesElement); + console.log('Devices element HTML:', devicesElement?.innerHTML); + + const contentDiv = document.querySelector('.content'); + console.log('\nContent div:', contentDiv); + console.log('Content div HTML:', contentDiv?.innerHTML); + + const deviceCards = document.querySelectorAll('.device.card'); + console.log('\nDevice cards found:', deviceCards.length); + + const footerDiv = document.querySelector('.footer'); + console.log('Footer div:', footerDiv); + console.log('Footer text:', footerDiv?.textContent); + + // Testy + const tests = { + 'App element exists': !!appElement, + 'Devices element exists': !!devicesElement, + 'Content div exists': !!contentDiv, + 'Device cards rendered': deviceCards.length === 3, + 'Footer exists': !!footerDiv, + 'Footer shows count': footerDiv?.textContent?.includes('3'), + }; + + console.log('\n=== Test Results ==='); + let passed = 0; + let failed = 0; + + Object.entries(tests).forEach(([name, result]) => { + const status = result ? '✓ PASS' : '✗ FAIL'; + console.log(`${status}: ${name}`); + if (result) passed++; + else failed++; + }); + + console.log(`\nTotal: ${passed} passed, ${failed} failed`); + + // Sprawdzenie czy template został przetworzony + const componentInstance = (devicesElement as any)?.componentInstance; + if (componentInstance) { + console.log('\n=== Component State ==='); + console.log('devices():', componentInstance.devices()); + console.log('deviceCount():', componentInstance.deviceCount()); + } + + // Sprawdzenie czy @for został przekształcony + const componentType = (devicesElement as any)?.componentType; + if (componentType) { + const template = componentType._quarcComponent?.[0]?.template; + console.log('\n=== Transformed Template ==='); + console.log(template); + + if (template) { + const hasNgFor = template.includes('*ngFor'); + const hasNgContainer = template.includes('ng-container'); + console.log('\nTemplate transformation check:'); + console.log(' Contains *ngFor:', hasNgFor); + console.log(' Contains ng-container:', hasNgContainer); + console.log(' @for was transformed:', hasNgFor && hasNgContainer); + } + } + + if (failed > 0) { + console.error('\n❌ DEVICES COMPONENT TEST FAILED - Component nie renderuje contentu'); + } else { + console.log('\n✅ DEVICES COMPONENT TEST PASSED'); + } + + document.body.removeChild(container); + }, 500); +} diff --git a/tests/unit/test-for-transformation.ts b/tests/unit/test-for-transformation.ts new file mode 100644 index 0000000..81590f8 --- /dev/null +++ b/tests/unit/test-for-transformation.ts @@ -0,0 +1,100 @@ +/** + * Test transformacji @for do *ngFor + * Reprodukuje problem z komponentu devices z /web/IoT/Ant + */ + +import { ControlFlowTransformer } from '../../cli/helpers/control-flow-transformer'; + +console.log('\n=== Test: @for Transformation ===\n'); + +const transformer = new ControlFlowTransformer(); + +// Test 1: Prosty @for jak w devices component +const template1 = ` +
+ @for (device of devices(); track device.address) { +
+
{{ device.name }}
+
+ } +
+`; + +console.log('Test 1: Prosty @for z track'); +console.log('Input:', template1); +const result1 = transformer.transform(template1); +console.log('Output:', result1); +console.log('Contains *ngFor:', result1.includes('*ngFor')); +console.log('Contains ng-container:', result1.includes('ng-container')); + +// Test 2: @for z wywołaniem funkcji (devices()) +const template2 = `@for (item of items(); track item.id) { +
{{ item.name }}
+}`; + +console.log('\n\nTest 2: @for z wywołaniem funkcji'); +console.log('Input:', template2); +const result2 = transformer.transform(template2); +console.log('Output:', result2); + +// Test 3: Zagnieżdżony @for +const template3 = ` +@for (device of devices(); track device.address) { +
+ @for (sensor of device.sensors; track sensor.id) { + {{ sensor.name }} + } +
+} +`; + +console.log('\n\nTest 3: Zagnieżdżony @for'); +console.log('Input:', template3); +const result3 = transformer.transform(template3); +console.log('Output:', result3); + +// Test 4: @for z interpolacją w środku +const template4 = ` +@for (device of devices(); track device.address) { +
+
{{ device.name || 'Unnamed' }}
+
{{ device.address }}
+
+} +`; + +console.log('\n\nTest 4: @for z interpolacją'); +console.log('Input:', template4); +const result4 = transformer.transform(template4); +console.log('Output:', result4); + +// Sprawdzenie czy wszystkie transformacje zawierają wymagane elementy +const tests = [ + { name: 'Test 1: *ngFor exists', pass: result1.includes('*ngFor') }, + { name: 'Test 1: ng-container exists', pass: result1.includes('ng-container') }, + { name: 'Test 1: track preserved', pass: result1.includes('track') || result1.includes('trackBy') }, + { name: 'Test 2: *ngFor exists', pass: result2.includes('*ngFor') }, + { name: 'Test 3: nested *ngFor exists', pass: (result3.match(/\*ngFor/g) || []).length === 2 }, + { name: 'Test 4: interpolation preserved', pass: result4.includes('device.name') }, +]; + +console.log('\n\n=== Test Results ==='); +let passed = 0; +let failed = 0; + +tests.forEach(test => { + const status = test.pass ? '✓ PASS' : '✗ FAIL'; + console.log(`${status}: ${test.name}`); + if (test.pass) passed++; + else failed++; +}); + +console.log(`\nTotal: ${passed} passed, ${failed} failed`); + +if (failed > 0) { + console.error('\n❌ @FOR TRANSFORMATION TEST FAILED'); + process.exit(1); +} else { + console.log('\n✅ @FOR TRANSFORMATION TEST PASSED'); + process.exit(0); +} diff --git a/tests/unit/test-interpolation-transformation.ts b/tests/unit/test-interpolation-transformation.ts new file mode 100644 index 0000000..9756dd3 --- /dev/null +++ b/tests/unit/test-interpolation-transformation.ts @@ -0,0 +1,133 @@ +/** + * Test transformacji interpolacji {{ }} + * Sprawdza czy interpolacja działa poprawnie po dodaniu obsługi pipes + */ + +import { TemplateTransformer } from '../../cli/processors/template/template-transformer'; + +console.log('\n=== Test: Interpolation Transformation ===\n'); + +const transformer = new TemplateTransformer(); + +// Test 1: Prosta interpolacja bez pipes +const template1 = `
{{ device.name }}
`; +console.log('Test 1: Prosta interpolacja'); +console.log('Input:', template1); +const result1 = transformer.transformInterpolation(template1); +console.log('Output:', result1); +console.log('Contains [innerText]:', result1.includes('[innerText]')); + +// Test 2: Interpolacja z operatorem || +const template2 = `
{{ device.name || 'Unnamed' }}
`; +console.log('\n\nTest 2: Interpolacja z operatorem ||'); +console.log('Input:', template2); +const result2 = transformer.transformInterpolation(template2); +console.log('Output:', result2); + +// Test 3: Interpolacja z wywołaniem funkcji +const template3 = `
{{ deviceCount() }}
`; +console.log('\n\nTest 3: Interpolacja z wywołaniem funkcji'); +console.log('Input:', template3); +const result3 = transformer.transformInterpolation(template3); +console.log('Output:', result3); + +// Test 4: Wiele interpolacji w jednym elemencie +const template4 = `
{{ device.name }} - {{ device.address }}
`; +console.log('\n\nTest 4: Wiele interpolacji'); +console.log('Input:', template4); +const result4 = transformer.transformInterpolation(template4); +console.log('Output:', result4); + +// Test 5: Interpolacja w atrybucie +const template5 = `
Content
`; +console.log('\n\nTest 5: Interpolacja w atrybucie'); +console.log('Input:', template5); +const result5 = transformer.transformInterpolation(template5); +console.log('Output:', result5); + +// Test 6: Pełny template jak w devices component +const template6 = ` +
+ @for (device of devices(); track device.address) { +
+
{{ device.name || 'Unnamed' }}
+
{{ device.address }}
+
+ } +
+ +`; +console.log('\n\nTest 6: Pełny template devices component'); +console.log('Input:', template6); +const result6 = transformer.transformAll(template6); +console.log('Output:', result6); + +// Sprawdzenie czy wszystkie transformacje są poprawne +const tests = [ + { + name: 'Test 1: Simple interpolation transformed', + pass: result1.includes('[innerText]') && result1.includes('device.name') + }, + { + name: 'Test 2: OR operator preserved', + pass: result2.includes('||') && result2.includes('Unnamed') + }, + { + name: 'Test 3: Function call preserved', + pass: result3.includes('deviceCount()') + }, + { + name: 'Test 4: Multiple interpolations', + pass: (result4.match(/\[innerText\]/g) || []).length === 2 + }, + { + name: 'Test 5: Attribute interpolation', + pass: result5.includes('data-quarc-attr-bindings') || result5.includes('[attr.title]') + }, + { + name: 'Test 6: Full template has *ngFor', + pass: result6.includes('*ngFor') + }, + { + name: 'Test 6: Full template has interpolations', + pass: result6.includes('[inner-text]') || result6.includes('[innerText]') + }, + { + name: 'Test 6: No pipe errors in simple expressions', + pass: !result6.includes('this._pipes') || result6.includes('|') + } +]; + +console.log('\n\n=== Test Results ==='); +let passed = 0; +let failed = 0; + +tests.forEach(test => { + const status = test.pass ? '✓ PASS' : '✗ FAIL'; + console.log(`${status}: ${test.name}`); + if (test.pass) passed++; + else failed++; +}); + +console.log(`\nTotal: ${passed} passed, ${failed} failed`); + +// Dodatkowa diagnostyka +console.log('\n\n=== Diagnostyka ==='); +console.log('Czy result1 zawiera this._pipes?:', result1.includes('this._pipes')); +console.log('Czy result2 zawiera this._pipes?:', result2.includes('this._pipes')); +console.log('Czy result3 zawiera this._pipes?:', result3.includes('this._pipes')); +console.log('\nResult6 check:'); +console.log('Zawiera [inner-text]?:', result6.includes('[inner-text]')); +console.log('Zawiera [innerText]?:', result6.includes('[innerText]')); +console.log('Liczba wystąpień [inner-text]:', (result6.match(/\[inner-text\]/g) || []).length); +console.log('Liczba wystąpień [innerText]:', (result6.match(/\[innerText\]/g) || []).length); + +if (failed > 0) { + console.error('\n❌ INTERPOLATION TRANSFORMATION TEST FAILED'); + process.exit(1); +} else { + console.log('\n✅ INTERPOLATION TRANSFORMATION TEST PASSED'); + process.exit(0); +} diff --git a/tests/unit/test-pipe-with-logical-operators.ts b/tests/unit/test-pipe-with-logical-operators.ts new file mode 100644 index 0000000..5332972 --- /dev/null +++ b/tests/unit/test-pipe-with-logical-operators.ts @@ -0,0 +1,88 @@ +/** + * Test aby upewnić się, że operatory logiczne (||, &&) nie są mylone z pipe separator | + */ + +import { TemplateTransformer } from '../../cli/processors/template/template-transformer'; + +console.log('\n=== Test: Pipe vs Logical Operators ===\n'); + +const transformer = new TemplateTransformer(); + +// Test 1: Operator || nie powinien być traktowany jako pipe +const test1 = `{{ value || 'default' }}`; +console.log('Test 1: Operator ||'); +console.log('Input:', test1); +const result1 = transformer.transformInterpolation(test1); +console.log('Output:', result1); +const pass1 = !result1.includes('this._pipes') && result1.includes('||'); +console.log('Pass:', pass1); + +// Test 2: Operator && nie powinien być traktowany jako pipe +const test2 = `{{ condition && value }}`; +console.log('\nTest 2: Operator &&'); +console.log('Input:', test2); +const result2 = transformer.transformInterpolation(test2); +console.log('Output:', result2); +const pass2 = !result2.includes('this._pipes') && result2.includes('&&'); +console.log('Pass:', pass2); + +// Test 3: Prawdziwy pipe powinien być transformowany +const test3 = `{{ value | uppercase }}`; +console.log('\nTest 3: Prawdziwy pipe'); +console.log('Input:', test3); +const result3 = transformer.transformInterpolation(test3); +console.log('Output:', result3); +const pass3 = result3.includes('this._pipes') && result3.includes('uppercase'); +console.log('Pass:', pass3); + +// Test 4: Pipe z argumentami +const test4 = `{{ value | slice:0:10 }}`; +console.log('\nTest 4: Pipe z argumentami'); +console.log('Input:', test4); +const result4 = transformer.transformInterpolation(test4); +console.log('Output:', result4); +const pass4 = result4.includes('this._pipes') && result4.includes('slice'); +console.log('Pass:', pass4); + +// Test 5: Kombinacja || i pipe +const test5 = `{{ (value || 'default') | uppercase }}`; +console.log('\nTest 5: Kombinacja || i pipe'); +console.log('Input:', test5); +const result5 = transformer.transformInterpolation(test5); +console.log('Output:', result5); +const pass5 = result5.includes('this._pipes') && result5.includes('||') && result5.includes('uppercase'); +console.log('Pass:', pass5); + +// Test 6: Wielokrotne || +const test6 = `{{ value1 || value2 || 'default' }}`; +console.log('\nTest 6: Wielokrotne ||'); +console.log('Input:', test6); +const result6 = transformer.transformInterpolation(test6); +console.log('Output:', result6); +const pass6 = !result6.includes('this._pipes') && (result6.match(/\|\|/g) || []).length === 2; +console.log('Pass:', pass6); + +// Test 7: Łańcuch pipes +const test7 = `{{ value | lowercase | slice:0:5 }}`; +console.log('\nTest 7: Łańcuch pipes'); +console.log('Input:', test7); +const result7 = transformer.transformInterpolation(test7); +console.log('Output:', result7); +const pass7 = result7.includes('lowercase') && result7.includes('slice'); +console.log('Pass:', pass7); + +const allTests = [pass1, pass2, pass3, pass4, pass5, pass6, pass7]; +const passed = allTests.filter(p => p).length; +const failed = allTests.length - passed; + +console.log('\n=== Summary ==='); +console.log(`Passed: ${passed}/${allTests.length}`); +console.log(`Failed: ${failed}/${allTests.length}`); + +if (failed > 0) { + console.error('\n❌ PIPE VS LOGICAL OPERATORS TEST FAILED'); + process.exit(1); +} else { + console.log('\n✅ PIPE VS LOGICAL OPERATORS TEST PASSED'); + process.exit(0); +}