improve control flow transformer robustness and remove debug logs

This commit is contained in:
Michał Sieciechowicz 2026-01-18 20:27:12 +01:00
parent 990aef92ef
commit c3dda73d3a
8 changed files with 287 additions and 69 deletions

View File

@ -56,10 +56,20 @@ export class ControlFlowTransformer {
closeParenIndex--; closeParenIndex--;
const openBraceIndex = content.indexOf('{', closeParenIndex); const openBraceIndex = content.indexOf('{', closeParenIndex);
if (openBraceIndex === -1) return null; if (openBraceIndex === -1) {
// Handle case where there's no opening brace - create a simple block
return {
match: content.substring(ifIndex, closeParenIndex + 1) + '{ }',
startIndex: ifIndex,
endIndex: closeParenIndex + 1
};
}
let endIndex = this.findIfBlockEnd(content, openBraceIndex); let endIndex = this.findIfBlockEnd(content, openBraceIndex);
if (endIndex === -1) return null; if (endIndex === -1) {
// For incomplete blocks, try to process what we have
endIndex = content.length;
}
return { return {
match: content.substring(ifIndex, endIndex), match: content.substring(ifIndex, endIndex),
@ -79,7 +89,21 @@ export class ControlFlowTransformer {
index++; index++;
} }
if (braceCount !== 0) return -1; // If we couldn't find matching closing brace, try to handle incomplete blocks
if (braceCount !== 0) {
// For incomplete blocks, find the end of the current line or next @if/@for statement
const remainingContent = content.substring(startBraceIndex);
const nextIfIndex = remainingContent.indexOf('@if', 1);
const nextForIndex = remainingContent.indexOf('@for', 1);
const lineEndIndex = remainingContent.indexOf('\n');
let endIndex = content.length;
if (nextIfIndex !== -1) endIndex = Math.min(endIndex, startBraceIndex + nextIfIndex);
if (nextForIndex !== -1) endIndex = Math.min(endIndex, startBraceIndex + nextForIndex);
if (lineEndIndex !== -1) endIndex = Math.min(endIndex, startBraceIndex + lineEndIndex);
return endIndex;
}
while (index < content.length) { while (index < content.length) {
const remaining = content.substring(index); const remaining = content.substring(index);
@ -165,7 +189,21 @@ export class ControlFlowTransformer {
if (forIndex === -1) return null; if (forIndex === -1) return null;
const openParenIndex = content.indexOf('(', forIndex); const openParenIndex = content.indexOf('(', forIndex);
const closeParenIndex = content.indexOf(')', openParenIndex); if (openParenIndex === -1) return null;
// Properly count nested parentheses
let parenCount = 1;
let closeParenIndex = openParenIndex + 1;
while (closeParenIndex < content.length && parenCount > 0) {
const char = content[closeParenIndex];
if (char === '(') parenCount++;
else if (char === ')') parenCount--;
closeParenIndex++;
}
if (parenCount !== 0) return null;
closeParenIndex--; // Move back to the closing paren
const openBraceIndex = content.indexOf('{', closeParenIndex); const openBraceIndex = content.indexOf('{', closeParenIndex);
if (openBraceIndex === -1) return null; if (openBraceIndex === -1) return null;
@ -194,7 +232,21 @@ export class ControlFlowTransformer {
if (startIndex === -1) return null; if (startIndex === -1) return null;
const openParenIndex = match.indexOf('(', startIndex); const openParenIndex = match.indexOf('(', startIndex);
const closeParenIndex = match.indexOf(')', openParenIndex); if (openParenIndex === -1) return null;
// Properly count nested parentheses
let parenCount = 1;
let closeParenIndex = openParenIndex + 1;
while (closeParenIndex < match.length && parenCount > 0) {
const char = match[closeParenIndex];
if (char === '(') parenCount++;
else if (char === ')') parenCount--;
closeParenIndex++;
}
if (parenCount !== 0) return null;
closeParenIndex--; // Move back to the closing paren
const openBraceIndex = match.indexOf('{', closeParenIndex); const openBraceIndex = match.indexOf('{', closeParenIndex);
if (openBraceIndex === -1) return null; if (openBraceIndex === -1) return null;
@ -219,7 +271,8 @@ export class ControlFlowTransformer {
const forPart = parts[0].trim(); const forPart = parts[0].trim();
const trackPart = parts[1]?.trim(); const trackPart = parts[1]?.trim();
const forMatch = forPart.match(/^\s*([^\s]+)\s+of\s+([^\s]+)\s*$/); // Match: variable of iterable (iterable can contain parentheses, dots, etc.)
const forMatch = forPart.match(/^\s*(\w+)\s+of\s+(.+)\s*$/);
if (!forMatch) return null; if (!forMatch) return null;
const variable = forMatch[1].trim(); const variable = forMatch[1].trim();
@ -248,7 +301,10 @@ export class ControlFlowTransformer {
ngForExpression += `; trackBy: ${forBlock.trackBy}`; ngForExpression += `; trackBy: ${forBlock.trackBy}`;
} }
return `<ng-container *ngFor="${ngForExpression}">${forBlock.content}</ng-container>`; // Recursively transform nested @if and @for blocks in content
const transformedContent = this.transform(forBlock.content);
return `<ng-container *ngFor="${ngForExpression}">${transformedContent}</ng-container>`;
} }
private parseBlocks(match: string): ControlFlowBlock[] { private parseBlocks(match: string): ControlFlowBlock[] {
@ -273,6 +329,12 @@ export class ControlFlowTransformer {
const { condition, aliasVariable } = this.parseConditionWithAlias(conditionStr.trim()); const { condition, aliasVariable } = this.parseConditionWithAlias(conditionStr.trim());
const openBraceIndex = match.indexOf('{', closeParenIndex); const openBraceIndex = match.indexOf('{', closeParenIndex);
if (openBraceIndex === -1) {
// Incomplete @if block - no opening brace found
blocks.push({ condition, content: '', aliasVariable });
return blocks;
}
let braceCount = 1; let braceCount = 1;
let closeBraceIndex = openBraceIndex + 1; let closeBraceIndex = openBraceIndex + 1;
@ -282,11 +344,22 @@ export class ControlFlowTransformer {
else if (char === '}') braceCount--; else if (char === '}') braceCount--;
closeBraceIndex++; closeBraceIndex++;
} }
closeBraceIndex--;
const content = match.substring(openBraceIndex + 1, closeBraceIndex); let content = '';
blocks.push({ condition, content, aliasVariable }); if (braceCount === 0) {
// Complete block found
closeBraceIndex--;
content = match.substring(openBraceIndex + 1, closeBraceIndex);
index = closeBraceIndex + 1; index = closeBraceIndex + 1;
} else {
// Incomplete block - take everything after opening brace
content = match.substring(openBraceIndex + 1);
index = match.length;
}
// Trim leading whitespace from content
content = content.trimStart();
blocks.push({ condition, content, aliasVariable });
} }
while (index < match.length) { while (index < match.length) {
@ -311,6 +384,13 @@ export class ControlFlowTransformer {
const { condition, aliasVariable } = this.parseConditionWithAlias(conditionStr.trim()); const { condition, aliasVariable } = this.parseConditionWithAlias(conditionStr.trim());
const openBraceIndex = match.indexOf('{', closeParenIndex); const openBraceIndex = match.indexOf('{', closeParenIndex);
if (openBraceIndex === -1) {
// Incomplete @else if block - no opening brace found
blocks.push({ condition, content: '', aliasVariable });
index = match.length;
break;
}
let braceCount = 1; let braceCount = 1;
let closeBraceIndex = openBraceIndex + 1; let closeBraceIndex = openBraceIndex + 1;
@ -320,11 +400,22 @@ export class ControlFlowTransformer {
else if (char === '}') braceCount--; else if (char === '}') braceCount--;
closeBraceIndex++; closeBraceIndex++;
} }
closeBraceIndex--;
const content = match.substring(openBraceIndex + 1, closeBraceIndex); let content = '';
blocks.push({ condition, content, aliasVariable }); if (braceCount === 0) {
// Complete block found
closeBraceIndex--;
content = match.substring(openBraceIndex + 1, closeBraceIndex);
index = closeBraceIndex + 1; index = closeBraceIndex + 1;
} else {
// Incomplete block - take everything after opening brace
content = match.substring(openBraceIndex + 1);
index = match.length;
}
// Trim leading whitespace from content
content = content.trimStart();
blocks.push({ condition, content, aliasVariable });
} else if (elseMatch) { } else if (elseMatch) {
const openBraceIndex = index + elseMatch[0].length - 1; const openBraceIndex = index + elseMatch[0].length - 1;
let braceCount = 1; let braceCount = 1;
@ -336,11 +427,22 @@ export class ControlFlowTransformer {
else if (char === '}') braceCount--; else if (char === '}') braceCount--;
closeBraceIndex++; closeBraceIndex++;
} }
closeBraceIndex--;
const content = match.substring(openBraceIndex + 1, closeBraceIndex); let content = '';
blocks.push({ condition: null, content }); if (braceCount === 0) {
// Complete block found
closeBraceIndex--;
content = match.substring(openBraceIndex + 1, closeBraceIndex);
index = closeBraceIndex + 1; index = closeBraceIndex + 1;
} else {
// Incomplete block - take everything after opening brace
content = match.substring(openBraceIndex + 1);
index = match.length;
}
// Trim leading whitespace from content
content = content.trimStart();
blocks.push({ condition: null, content });
} else { } else {
break; break;
} }
@ -368,10 +470,13 @@ export class ControlFlowTransformer {
const block = blocks[i]; const block = blocks[i];
const condition = this.buildCondition(block.condition, negated); const condition = this.buildCondition(block.condition, negated);
// Recursively transform nested @if and @for blocks in content
const transformedContent = this.transform(block.content);
if (block.aliasVariable) { if (block.aliasVariable) {
result += `<ng-container *ngIf="${condition}; let ${block.aliasVariable}">${block.content}</ng-container>`; result += `<ng-container *ngIf="${condition}; let ${block.aliasVariable}">${transformedContent}</ng-container>`;
} else { } else {
result += `<ng-container *ngIf="${condition}">${block.content}</ng-container>`; result += `<ng-container *ngIf="${condition}">${transformedContent}</ng-container>`;
} }
if (i < blocks.length - 1) { if (i < blocks.length - 1) {

View File

@ -160,6 +160,7 @@ class Server extends BaseBuilder {
} }
private proxyRequest(targetUrl: string, req: http.IncomingMessage, res: http.ServerResponse): void { private proxyRequest(targetUrl: string, req: http.IncomingMessage, res: http.ServerResponse): void {
console.log(`[Proxy] ${req.method} ${req.url} -> ${targetUrl}`);
const parsedUrl = new URL(targetUrl); const parsedUrl = new URL(targetUrl);
const protocol = parsedUrl.protocol === 'https:' ? https : http; const protocol = parsedUrl.protocol === 'https:' ? https : http;
@ -173,13 +174,14 @@ class Server extends BaseBuilder {
}, },
}, },
(proxyRes) => { (proxyRes) => {
console.log(`[Proxy] Response: ${proxyRes.statusCode} for ${req.url}`);
res.writeHead(proxyRes.statusCode || 500, proxyRes.headers); res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
proxyRes.pipe(res); proxyRes.pipe(res);
}, },
); );
proxyReq.on('error', (err) => { proxyReq.on('error', (err) => {
console.error('Proxy error:', err.message); console.error(`[Proxy] Error for ${req.url}:`, err.message);
res.writeHead(502); res.writeHead(502);
res.end('Bad Gateway'); res.end('Bad Gateway');
}); });

View File

@ -61,27 +61,11 @@ export class Injector {
} }
public createInstanceWithProviders<T>(classType: Type<T>, providers: Provider[]): T { public createInstanceWithProviders<T>(classType: Type<T>, providers: Provider[]): T {
console.log('[DI] createInstanceWithProviders START', {
classType,
typeofClassType: typeof classType,
isFunction: typeof classType === 'function',
hasName: classType?.name,
originalName: (classType as any)?.__quarc_original_name__,
providers: providers.map(p => ({
provide: typeof p.provide === 'string' ? p.provide : p.provide?.name,
type: 'useValue' in p ? 'useValue' : 'useClass' in p ? 'useClass' : 'useFactory' in p ? 'useFactory' : 'useExisting'
}))
});
if (!classType) { if (!classType) {
throw new Error(`[DI] createInstanceWithProviders called with undefined classType`); throw new Error(`[DI] createInstanceWithProviders called with undefined classType`);
} }
try { try {
console.log({
className: (classType as any).__quarc_original_name__ || classType.name,
classType,
});
const dependencies = this.resolveDependenciesWithProviders(classType, providers); const dependencies = this.resolveDependenciesWithProviders(classType, providers);
/** / /** /
console.log({ console.log({
@ -127,11 +111,6 @@ export class Injector {
return `${metadata.selector} (class)`; return `${metadata.selector} (class)`;
} }
console.log({
classType,
metadata,
});
return 'Unknown class'; return 'Unknown class';
} }
@ -187,44 +166,27 @@ export class Injector {
} }
private resolveDependency(token: any, providers: Provider[]): any { private resolveDependency(token: any, providers: Provider[]): any {
console.log('[DI] resolveDependency', {
token,
typeofToken: typeof token,
tokenName: typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token?.name,
isFunction: typeof token === 'function',
});
const tokenName = typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token.name; const tokenName = typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token.name;
const provider = this.findProvider(token, providers); const provider = this.findProvider(token, providers);
if (provider) { if (provider) {
console.log('[DI] Found provider for token', tokenName);
return this.resolveProviderValue(provider, providers); return this.resolveProviderValue(provider, providers);
} }
if (this.sharedInstances[tokenName]) { if (this.sharedInstances[tokenName]) {
console.log('[DI] Found in sharedInstances', tokenName);
return this.sharedInstances[tokenName]; return this.sharedInstances[tokenName];
} }
if (this.instanceCache[tokenName]) { if (this.instanceCache[tokenName]) {
console.log('[DI] Found in instanceCache', tokenName);
return this.instanceCache[tokenName]; return this.instanceCache[tokenName];
} }
console.log('[DI] Creating new instance for token', tokenName);
return this.createInstanceWithProviders(token, providers); return this.createInstanceWithProviders(token, providers);
} }
private getConstructorParameterTypes(classType: Type<any>): any[] { private getConstructorParameterTypes(classType: Type<any>): any[] {
const className = classType?.name || 'Unknown'; const className = classType?.name || 'Unknown';
console.log({
className,
classType,
diParams: (classType as any).__di_params__,
});
if (!classType) { if (!classType) {
throw new Error(`[DI] Cannot resolve dependencies: classType is undefined`); throw new Error(`[DI] Cannot resolve dependencies: classType is undefined`);
} }
@ -256,12 +218,10 @@ export class Injector {
public register<T>(classType: Type<T>, instance: T | Type<T>): void { public register<T>(classType: Type<T>, instance: T | Type<T>): void {
const key = (classType as any).__quarc_original_name__ || classType.name; const key = (classType as any).__quarc_original_name__ || classType.name;
this.instanceCache[key] = instance; this.instanceCache[key] = instance;
console.log('injector register', classType, key, instance);
} }
public registerShared<T>(classType: Type<T>, instance: T | Type<T>): void { public registerShared<T>(classType: Type<T>, instance: T | Type<T>): void {
const key = (classType as any).__quarc_original_name__ || classType.name; const key = (classType as any).__quarc_original_name__ || classType.name;
console.log('injector registerShared', classType, key, instance);
this.sharedInstances[key] = instance; this.sharedInstances[key] = instance;
} }

View File

@ -235,7 +235,6 @@ export class TemplateFragment {
const isForIn = !!forInMatch; const isForIn = !!forInMatch;
if (!match) { if (!match) {
console.warn('Invalid ngFor expression:', ngForExpression);
parent.insertBefore(endMarker, ngContainer); parent.insertBefore(endMarker, ngContainer);
ngContainer.remove(); ngContainer.remove();
return; return;
@ -376,7 +375,6 @@ export class TemplateFragment {
*/ */
rerenderFragment(markerIndex: number): void { rerenderFragment(markerIndex: number): void {
if (markerIndex < 0 || markerIndex >= this.ngContainerMarkers.length) { if (markerIndex < 0 || markerIndex >= this.ngContainerMarkers.length) {
console.warn('Invalid marker index:', markerIndex);
return; return;
} }

View File

@ -24,7 +24,6 @@ export class WebComponentFactory {
const componentMeta = componentType._quarcComponent?.[0]; const componentMeta = componentType._quarcComponent?.[0];
if (!componentMeta) { if (!componentMeta) {
console.warn(`Component ${componentType.name} has no _quarcComponent metadata`);
return false; return false;
} }
@ -69,7 +68,6 @@ export class WebComponentFactory {
this.componentTypes.set(tagName, componentType); this.componentTypes.set(tagName, componentType);
return true; return true;
} catch (error) { } catch (error) {
console.warn(`Failed to register component ${tagName}:`, error);
return false; return false;
} }
} }
@ -108,7 +106,6 @@ export class WebComponentFactory {
} }
} }
console.log({localProviders});
return injector.createInstanceWithProviders<IComponent>(componentType, localProviders); return injector.createInstanceWithProviders<IComponent>(componentType, localProviders);
} }

View File

@ -35,11 +35,8 @@ async function tryLoadExternalScripts(urls: string | string[]): Promise<void> {
await loadExternalScript(url); await loadExternalScript(url);
return; return;
} catch { } catch {
console.warn(`[External] Could not load from: ${url}`);
} }
} }
console.info("[External] No external scripts loaded - app continues without enhancements");
} }
export async function bootstrapApplication( export async function bootstrapApplication(

View File

@ -15,7 +15,6 @@ export class RouterLink implements IDirective {
public _nativeElement: HTMLElement, public _nativeElement: HTMLElement,
private activatedRoute?: ActivatedRoute, private activatedRoute?: ActivatedRoute,
) { ) {
console.log({ routerLink: this.routerLink() });
this._nativeElement.addEventListener('click', (event) => { this._nativeElement.addEventListener('click', (event) => {
this.onClick(event); this.onClick(event);
}); });
@ -44,7 +43,6 @@ export class RouterLink implements IDirective {
this.router.navigate(commands, extras).then(success => { this.router.navigate(commands, extras).then(success => {
}).catch(error => { }).catch(error => {
console.error('RouterLink CLICK - Navigation failed:', error);
}); });
} }

View File

@ -261,6 +261,167 @@ test('ControlFlowTransformer: @if @else if oba z aliasem', () => {
result.includes('*ngIf="!(primary()) && !(secondary())"'); result.includes('*ngIf="!(primary()) && !(secondary())"');
}); });
// Test 25: ControlFlowTransformer - niekompletny @if (bez zamknięcia)
test('ControlFlowTransformer: niekompletny @if', () => {
const transformer = new ControlFlowTransformer();
const input = '@if (range.name) { content';
const result = transformer.transform(input);
return result.includes('<ng-container *ngIf="range.name">content</ng-container>');
});
// Test 26: ControlFlowTransformer - niekompletny @if na końcu
test('ControlFlowTransformer: niekompletny @if na końcu', () => {
const transformer = new ControlFlowTransformer();
const input = '} @if (prepared.sensor.loading) { ';
const result = transformer.transform(input);
return result.includes('<ng-container *ngIf="prepared.sensor.loading"></ng-container>');
});
// Test 27: ControlFlowTransformer - niekompletny @if bez nawiasu klamrowego
test('ControlFlowTransformer: niekompletny @if bez nawiasu klamrowego', () => {
const transformer = new ControlFlowTransformer();
const input = '@if (condition) ';
const result = transformer.transform(input);
return result.includes('<ng-container *ngIf="condition"></ng-container>');
});
// Test 28: ControlFlowTransformer - zagnieżdżony @if wewnątrz @for
test('ControlFlowTransformer: zagnieżdżony @if wewnątrz @for', () => {
const transformer = new ControlFlowTransformer();
const input = `@for (item of items) {
@if (item.active) {
<span>{{ item.name }}</span>
}
}`;
const result = transformer.transform(input);
return result.includes('<ng-container *ngFor="let item of items">') &&
result.includes('<ng-container *ngIf="item.active">') &&
!result.includes('@if') &&
!result.includes('@for');
});
// Test 29: ControlFlowTransformer - zagnieżdżony @for wewnątrz @if
test('ControlFlowTransformer: zagnieżdżony @for wewnątrz @if', () => {
const transformer = new ControlFlowTransformer();
const input = `@if (hasItems) {
@for (item of items; track item.id) {
<div>{{ item }}</div>
}
}`;
const result = transformer.transform(input);
return result.includes('<ng-container *ngIf="hasItems">') &&
result.includes('<ng-container *ngFor="let item of items; trackBy: item.id">') &&
!result.includes('@if') &&
!result.includes('@for');
});
// Test 30: ControlFlowTransformer - wielokrotnie zagnieżdżone @if wewnątrz @for
test('ControlFlowTransformer: wielokrotnie zagnieżdżone @if wewnątrz @for', () => {
const transformer = new ControlFlowTransformer();
const input = `@for (range of ranges; track $index) {
<div>
@if (range.name) {
<span>{{ range.name }}</span>
}
<span>{{ range.min }}</span>
</div>
}`;
const result = transformer.transform(input);
return result.includes('<ng-container *ngFor="let range of ranges; trackBy: $index">') &&
result.includes('<ng-container *ngIf="range.name">') &&
!result.includes('@if') &&
!result.includes('@for');
});
// Test 31: ControlFlowTransformer - kompleksowy przypadek z user template
test('ControlFlowTransformer: kompleksowy przypadek użytkownika', () => {
const transformer = new ControlFlowTransformer();
const input = `@if (prepared.sensor.ranges) {
<span>
@for (range of prepared.sensor.ranges; track $index) {
<div>
@if (range.name) {
<span>{{ range.name }}</span>
}
<span>{{ range.min }}</span>
<span>{{ range.max }}</span>
</div>
}
</span>
}`;
const result = transformer.transform(input);
return result.includes('<ng-container *ngIf="prepared.sensor.ranges">') &&
result.includes('<ng-container *ngFor="let range of prepared.sensor.ranges; trackBy: $index">') &&
result.includes('<ng-container *ngIf="range.name">') &&
!result.includes('@if') &&
!result.includes('@for');
});
// Test 32: ControlFlowTransformer - głęboko zagnieżdżone @if/@for/@if
test('ControlFlowTransformer: głęboko zagnieżdżone @if/@for/@if', () => {
const transformer = new ControlFlowTransformer();
const input = `@if (level1) {
@for (item of items; track item.id) {
@if (item.visible) {
<div>{{ item.name }}</div>
}
}
}`;
const result = transformer.transform(input);
return result.includes('<ng-container *ngIf="level1">') &&
result.includes('<ng-container *ngFor="let item of items; trackBy: item.id">') &&
result.includes('<ng-container *ngIf="item.visible">') &&
!result.includes('@if') &&
!result.includes('@for');
});
// Test 33: ControlFlowTransformer - @for z funkcją w iterable
test('ControlFlowTransformer: @for z funkcją w iterable', () => {
const transformer = new ControlFlowTransformer();
const input = '@for (item of getItems(); track item.id) { <div>{{ item }}</div> }';
const result = transformer.transform(input);
return result.includes('<ng-container *ngFor="let item of getItems(); trackBy: item.id">') &&
!result.includes('@for');
});
// Test 34: ControlFlowTransformer - @for z zagnieżdżonymi nawiasami w funkcji
test('ControlFlowTransformer: @for z zagnieżdżonymi nawiasami w funkcji', () => {
const transformer = new ControlFlowTransformer();
const input = '@for (prepared of preparedSensors(); track prepared.sensor.id) { <div>{{ prepared.sensor.name }}</div> }';
const result = transformer.transform(input);
return result.includes('<ng-container *ngFor="let prepared of preparedSensors(); trackBy: prepared.sensor.id">') &&
result.includes('prepared.sensor.name') &&
!result.includes('@for');
});
// Test 35: ControlFlowTransformer - @for z metodą obiektu w iterable
test('ControlFlowTransformer: @for z metodą obiektu w iterable', () => {
const transformer = new ControlFlowTransformer();
const input = '@for (item of data.getItems(); track $index) { <span>{{ item }}</span> }';
const result = transformer.transform(input);
return result.includes('<ng-container *ngFor="let item of data.getItems(); trackBy: $index">') &&
!result.includes('@for');
});
// Test 36: ControlFlowTransformer - @for z wieloma zagnieżdżonymi nawiasami
test('ControlFlowTransformer: @for z wieloma zagnieżdżonymi nawiasami', () => {
const transformer = new ControlFlowTransformer();
const input = '@for (item of service.getData(filter(value())); track item.id) { <div>{{ item }}</div> }';
const result = transformer.transform(input);
return result.includes('<ng-container *ngFor="let item of service.getData(filter(value())); trackBy: item.id">') &&
!result.includes('@for');
});
// Test 37: ControlFlowTransformer - @for z funkcją i złożonym trackBy
test('ControlFlowTransformer: @for z funkcją i złożonym trackBy', () => {
const transformer = new ControlFlowTransformer();
const input = '@for (range of prepared.sensor.ranges; track $index) { <div>{{ range.name }}</div> }';
const result = transformer.transform(input);
return result.includes('<ng-container *ngFor="let range of prepared.sensor.ranges; trackBy: $index">') &&
result.includes('range.name') &&
!result.includes('@for');
});
console.log('\n=== PODSUMOWANIE ==='); console.log('\n=== PODSUMOWANIE ===');
console.log(`✅ Testy zaliczone: ${passedTests}`); console.log(`✅ Testy zaliczone: ${passedTests}`);
console.log(`❌ Testy niezaliczone: ${failedTests}`); console.log(`❌ Testy niezaliczone: ${failedTests}`);