pipes
This commit is contained in:
parent
c3dda73d3a
commit
ba47312f55
|
|
@ -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
|
||||
<span [inner-text]="device.name || 'Unnamed'"></span>
|
||||
```
|
||||
|
||||
### Prawdziwy pipe (poprawnie transformowany)
|
||||
```typescript
|
||||
// Input
|
||||
{{ value | uppercase }}
|
||||
|
||||
// Output
|
||||
<span [inner-text]="this._pipes?.['uppercase']?.transform(value)"></span>
|
||||
```
|
||||
|
||||
### Kombinacja || i pipe
|
||||
```typescript
|
||||
// Input
|
||||
{{ (value || 'default') | uppercase }}
|
||||
|
||||
// Output
|
||||
<span [inner-text]="this._pipes?.['uppercase']?.transform((value || 'default'))"></span>
|
||||
```
|
||||
|
||||
### Łańcuch pipes
|
||||
```typescript
|
||||
// Input
|
||||
{{ value | lowercase | slice:0:5 }}
|
||||
|
||||
// Output
|
||||
<span [inner-text]="this._pipes?.['slice']?.transform(this._pipes?.['lowercase']?.transform(value), 0, 5)"></span>
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
|
@ -40,19 +40,25 @@ 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(', ')}];`;
|
||||
}
|
||||
|
||||
if (insert) {
|
||||
replacements.push({
|
||||
position: scopeIdEnd,
|
||||
insert: directivesProperty,
|
||||
insert,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = replacements.length - 1; i >= 0; i--) {
|
||||
const r = replacements[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(',')
|
||||
|
|
|
|||
|
|
@ -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) => `<span [innerText]="${expr.trim()}"></span>`,
|
||||
(_, expr) => {
|
||||
const transformedExpr = this.transformPipeExpression(expr.trim());
|
||||
return `<span [innerText]="${transformedExpr}"></span>`;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<string, Type<any>>();
|
||||
private pipeMetadata = new Map<Type<any>, PipeMetadata>();
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static get(): PipeRegistry {
|
||||
if (!PipeRegistry.instance) {
|
||||
PipeRegistry.instance = new PipeRegistry();
|
||||
}
|
||||
return PipeRegistry.instance;
|
||||
}
|
||||
|
||||
register(pipeType: Type<any>): 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<any> | undefined {
|
||||
return this.pipes.get(name);
|
||||
}
|
||||
|
||||
getPipeMetadata(pipeType: Type<any>): PipeMetadata | undefined {
|
||||
return this.pipeMetadata.get(pipeType);
|
||||
}
|
||||
|
||||
getAllPipes(): Map<string, Type<any>> {
|
||||
return new Map(this.pipes);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ export interface Type<T> {
|
|||
export interface ComponentType<T> extends Type<T> {
|
||||
_quarcComponent: [ComponentOptions];
|
||||
_quarcDirectives?: DirectiveType<any>[];
|
||||
_quarcPipes?: Type<any>[];
|
||||
_scopeId: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string, any> = {};
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test: Devices Component</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
padding: 20px;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
.device.card {
|
||||
border: 1px solid #444;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border-top: 1px solid #444;
|
||||
}
|
||||
#test-output {
|
||||
background: #252526;
|
||||
padding: 15px;
|
||||
margin-top: 20px;
|
||||
border: 1px solid #444;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test: Devices Component Rendering</h1>
|
||||
<div id="app-container"></div>
|
||||
<div id="test-output"></div>
|
||||
|
||||
<script type="module">
|
||||
import { runDevicesComponentTests } from './compiled/test-devices-component.js';
|
||||
|
||||
const output = document.getElementById('test-output');
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
|
||||
console.log = (...args) => {
|
||||
originalLog(...args);
|
||||
output.textContent += args.join(' ') + '\n';
|
||||
};
|
||||
|
||||
console.error = (...args) => {
|
||||
originalError(...args);
|
||||
output.textContent += 'ERROR: ' + args.join(' ') + '\n';
|
||||
};
|
||||
|
||||
runDevicesComponentTests();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import { Component, signal, OnInit } from "../../core/index";
|
||||
import { bootstrapApplication } from "../../platform-browser/browser";
|
||||
|
||||
// Symulacja IconComponent
|
||||
@Component({
|
||||
selector: 'test-icon',
|
||||
template: '<span>Icon</span>',
|
||||
})
|
||||
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: `
|
||||
<div class="content">
|
||||
@for (device of devices(); track device.address) {
|
||||
<div class="device card" (click)="openDevice(device.address)">
|
||||
<div class="icon">
|
||||
<test-icon></test-icon>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="name">{{ device.name || 'Unnamed' }}</div>
|
||||
<div class="address">{{ device.address }}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="footer">
|
||||
Devices: <span>{{ deviceCount() }}</span>
|
||||
</div>
|
||||
`,
|
||||
imports: [TestIconComponent],
|
||||
})
|
||||
class TestDevicesComponent implements OnInit {
|
||||
public devices = signal<Device[]>([]);
|
||||
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: '<test-devices></test-devices>',
|
||||
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);
|
||||
}
|
||||
|
|
@ -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 = `
|
||||
<div class="content">
|
||||
@for (device of devices(); track device.address) {
|
||||
<div class="device card">
|
||||
<div class="name">{{ device.name }}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
|
||||
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) {
|
||||
<div>{{ item.name }}</div>
|
||||
}`;
|
||||
|
||||
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) {
|
||||
<div>
|
||||
@for (sensor of device.sensors; track sensor.id) {
|
||||
<span>{{ sensor.name }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
`;
|
||||
|
||||
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) {
|
||||
<div class="device">
|
||||
<div>{{ device.name || 'Unnamed' }}</div>
|
||||
<div>{{ device.address }}</div>
|
||||
</div>
|
||||
}
|
||||
`;
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -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 = `<div>{{ device.name }}</div>`;
|
||||
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 = `<div>{{ device.name || 'Unnamed' }}</div>`;
|
||||
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 = `<div>{{ deviceCount() }}</div>`;
|
||||
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 = `<div>{{ device.name }} - {{ device.address }}</div>`;
|
||||
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 = `<div title="{{ device.name }}">Content</div>`;
|
||||
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 = `
|
||||
<div class="content">
|
||||
@for (device of devices(); track device.address) {
|
||||
<div class="device card">
|
||||
<div class="name">{{ device.name || 'Unnamed' }}</div>
|
||||
<div class="address">{{ device.address }}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="footer">
|
||||
Devices: <span>{{ deviceCount() }}</span>
|
||||
</div>
|
||||
`;
|
||||
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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in New Issue