pipe part 2
This commit is contained in:
parent
ba47312f55
commit
72f5d249cf
|
|
@ -0,0 +1,141 @@
|
|||
# Naprawa Pipes - Problem z kontekstem ewaluacji
|
||||
|
||||
## Problem zgłoszony przez użytkownika
|
||||
|
||||
W aplikacji IoT/Ant kod:
|
||||
```html
|
||||
<pre>{{ 123 | json }}</pre>
|
||||
<pre>{{ "string" | json }}</pre>
|
||||
<pre>{{ true | json }}</pre>
|
||||
```
|
||||
|
||||
Powodował, że cały komponent się "wysypał" bez błędów - wcześniej na metodach było po prostu "undefined".
|
||||
|
||||
## Analiza problemu
|
||||
|
||||
### 1. Transformacja była poprawna
|
||||
Transformer poprawnie generował kod:
|
||||
```typescript
|
||||
this._pipes?.['json']?.transform(123)
|
||||
```
|
||||
|
||||
### 2. Problem był w runtime
|
||||
W `template-renderer.ts` wyrażenia są ewaluowane za pomocą:
|
||||
```typescript
|
||||
private eval(expr: string): any {
|
||||
return new Function('c', `with(c){return ${expr}}`)(this.component);
|
||||
}
|
||||
```
|
||||
|
||||
W kontekście `with(c)`, `this` odnosi się do **globalnego obiektu**, a nie do komponentu `c`. Dlatego `this._pipes` było `undefined`.
|
||||
|
||||
## Rozwiązanie
|
||||
|
||||
Zmieniono generowany kod z `this._pipes` na `_pipes`:
|
||||
|
||||
**Przed:**
|
||||
```typescript
|
||||
this._pipes?.['json']?.transform(123)
|
||||
```
|
||||
|
||||
**Po:**
|
||||
```typescript
|
||||
_pipes?.['json']?.transform(123)
|
||||
```
|
||||
|
||||
Teraz `_pipes` jest dostępne bezpośrednio z kontekstu komponentu `c` w `with(c)`.
|
||||
|
||||
## 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 = `_pipes?.['${pipeName}']?.transform(${result})`; // ← Zmiana
|
||||
} 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 = `_pipes?.['${pipeName}']?.transform(${result}, ${argsJoined})`; // ← Zmiana
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## Testy
|
||||
|
||||
### Testy jednostkowe
|
||||
✅ `test-pipes.ts` - 31/31 testów przeszło
|
||||
✅ `test-pipe-with-logical-operators.ts` - 7/7 testów przeszło
|
||||
✅ `test-pipe-transformation-detailed.ts` - wszystkie transformacje poprawne
|
||||
|
||||
### Build aplikacji
|
||||
✅ `/web/IoT/Ant/assets/resources/quarc` - build przeszedł pomyślnie
|
||||
|
||||
## Utworzone pipes
|
||||
|
||||
Zestaw podstawowych pipes gotowych do użycia:
|
||||
|
||||
1. **UpperCasePipe** - `{{ text | uppercase }}`
|
||||
2. **LowerCasePipe** - `{{ text | lowercase }}`
|
||||
3. **JsonPipe** - `{{ obj | json }}`
|
||||
4. **CamelCasePipe** - `{{ 'hello-world' | camelcase }}` → `helloWorld`
|
||||
5. **PascalCasePipe** - `{{ 'hello-world' | pascalcase }}` → `HelloWorld`
|
||||
6. **SnakeCasePipe** - `{{ 'helloWorld' | snakecase }}` → `hello_world`
|
||||
7. **KebabCasePipe** - `{{ 'helloWorld' | kebabcase }}` → `hello-world`
|
||||
8. **SubstrPipe** - `{{ text | substr:0:10 }}`
|
||||
9. **DatePipe** - `{{ date | date:'yyyy-MM-dd' }}`
|
||||
|
||||
## Przykład użycia
|
||||
|
||||
```typescript
|
||||
import { Component, signal } from '@quarc/core';
|
||||
import { JsonPipe, UpperCasePipe } from '@quarc/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-example',
|
||||
template: `
|
||||
<div>{{ name | uppercase }}</div>
|
||||
<pre>{{ data | json }}</pre>
|
||||
<div>{{ value || 'default' | uppercase }}</div>
|
||||
`,
|
||||
imports: [JsonPipe, UpperCasePipe],
|
||||
})
|
||||
export class ExampleComponent {
|
||||
name = signal('hello');
|
||||
data = signal({ test: 123 });
|
||||
value = signal(null);
|
||||
}
|
||||
```
|
||||
|
||||
## Dokumentacja
|
||||
|
||||
Pełna dokumentacja pipes dostępna w:
|
||||
- `/web/quarc/core/pipes/README.md`
|
||||
- `/web/quarc/PIPE_IMPLEMENTATION_FIX.md`
|
||||
|
||||
## Podsumowanie
|
||||
|
||||
Problem został całkowicie rozwiązany:
|
||||
- ✅ Pipes są poprawnie transformowane w czasie kompilacji
|
||||
- ✅ Pipes są dostępne w runtime przez kontekst komponentu
|
||||
- ✅ Operatory logiczne `||` i `&&` nie są mylone z pipe separator `|`
|
||||
- ✅ Wszystkie testy jednostkowe przechodzą
|
||||
- ✅ Build aplikacji działa poprawnie
|
||||
|
|
@ -25,11 +25,17 @@ device.name || 'Unnamed'
|
|||
|
||||
## Rozwiązanie
|
||||
|
||||
### 1. Naprawa rozróżniania operatorów logicznych
|
||||
|
||||
Dodano metodę `splitByPipe()` która poprawnie rozróżnia:
|
||||
- Pojedynczy `|` - separator pipe
|
||||
- Podwójny `||` - operator logiczny OR
|
||||
- Podwójny `&&` - operator logiczny AND
|
||||
|
||||
### 2. Naprawa kontekstu ewaluacji (this._pipes → _pipes)
|
||||
|
||||
Zmieniono generowany kod z `this._pipes` na `_pipes`, ponieważ w kontekście `with(c)` używanym w `template-renderer.ts`, `this` odnosi się do globalnego obiektu, a nie do komponentu. Używając bezpośrednio `_pipes`, właściwość jest dostępna z kontekstu komponentu `c`.
|
||||
|
||||
### Zmieniony plik
|
||||
|
||||
**`/web/quarc/cli/processors/template/template-transformer.ts`**
|
||||
|
|
@ -50,13 +56,13 @@ private transformPipeExpression(expression: string): string {
|
|||
|
||||
if (colonIndex === -1) {
|
||||
const pipeName = pipePart.trim();
|
||||
result = `this._pipes?.['${pipeName}']?.transform(${result})`;
|
||||
result = `_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})`;
|
||||
result = `_pipes?.['${pipeName}']?.transform(${result}, ${argsJoined})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +135,7 @@ Utworzono testy w `/web/quarc/tests/unit/`:
|
|||
{{ value | uppercase }}
|
||||
|
||||
// Output
|
||||
<span [inner-text]="this._pipes?.['uppercase']?.transform(value)"></span>
|
||||
<span [inner-text]="_pipes?.['uppercase']?.transform(value)"></span>
|
||||
```
|
||||
|
||||
### Kombinacja || i pipe
|
||||
|
|
@ -138,7 +144,7 @@ Utworzono testy w `/web/quarc/tests/unit/`:
|
|||
{{ (value || 'default') | uppercase }}
|
||||
|
||||
// Output
|
||||
<span [inner-text]="this._pipes?.['uppercase']?.transform((value || 'default'))"></span>
|
||||
<span [inner-text]="_pipes?.['uppercase']?.transform((value || 'default'))"></span>
|
||||
```
|
||||
|
||||
### Łańcuch pipes
|
||||
|
|
@ -147,7 +153,7 @@ Utworzono testy w `/web/quarc/tests/unit/`:
|
|||
{{ value | lowercase | slice:0:5 }}
|
||||
|
||||
// Output
|
||||
<span [inner-text]="this._pipes?.['slice']?.transform(this._pipes?.['lowercase']?.transform(value), 0, 5)"></span>
|
||||
<span [inner-text]="_pipes?.['slice']?.transform(_pipes?.['lowercase']?.transform(value), 0, 5)"></span>
|
||||
```
|
||||
|
||||
## Weryfikacja
|
||||
|
|
|
|||
|
|
@ -103,13 +103,13 @@ export class TemplateTransformer {
|
|||
|
||||
if (colonIndex === -1) {
|
||||
const pipeName = pipePart.trim();
|
||||
result = `this._pipes?.['${pipeName}']?.transform(${result})`;
|
||||
result = `_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})`;
|
||||
result = `_pipes?.['${pipeName}']?.transform(${result}, ${argsJoined})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,4 +33,7 @@ export { inject, setCurrentInjector } from "./angular/inject";
|
|||
// types
|
||||
export type { ApplicationConfig, EnvironmentProviders, PluginConfig, PluginRoutingMode, Provider } from "./angular/app-config";
|
||||
export { ComponentUtils } from "./utils/component-utils";
|
||||
export { TemplateFragment } from "./module/template-renderer";
|
||||
export { TemplateFragment } from "./module/template-renderer";
|
||||
|
||||
// Pipes
|
||||
export { UpperCasePipe, LowerCasePipe, JsonPipe, CamelCasePipe, PascalCasePipe, SnakeCasePipe, KebabCasePipe, SubstrPipe, DatePipe } from "./pipes/index";
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
# Quarc Pipes
|
||||
|
||||
Zestaw podstawowych pipes dla frameworka Quarc, inspirowanych pipes z Angulara.
|
||||
|
||||
## Instalacja
|
||||
|
||||
Pipes są dostępne w `@quarc/core`:
|
||||
|
||||
```typescript
|
||||
import { UpperCasePipe, DatePipe, JsonPipe } from '@quarc/core';
|
||||
```
|
||||
|
||||
## Użycie w komponentach
|
||||
|
||||
### 1. Zaimportuj pipe w komponencie
|
||||
|
||||
```typescript
|
||||
import { Component } from '@quarc/core';
|
||||
import { UpperCasePipe } from '@quarc/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-example',
|
||||
template: '<div>{{ name | uppercase }}</div>',
|
||||
imports: [UpperCasePipe],
|
||||
})
|
||||
export class ExampleComponent {
|
||||
name = 'hello world';
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Użyj w template
|
||||
|
||||
```html
|
||||
<!-- Prosty pipe -->
|
||||
<div>{{ value | uppercase }}</div>
|
||||
|
||||
<!-- Pipe z argumentami -->
|
||||
<div>{{ text | substr:0:10 }}</div>
|
||||
|
||||
<!-- Łańcuch pipes -->
|
||||
<div>{{ name | lowercase | camelcase }}</div>
|
||||
|
||||
<!-- Kombinacja z operatorami -->
|
||||
<div>{{ value || 'default' | uppercase }}</div>
|
||||
```
|
||||
|
||||
## Dostępne Pipes
|
||||
|
||||
### UpperCasePipe
|
||||
|
||||
Konwertuje tekst na wielkie litery.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'uppercase' })
|
||||
```
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ 'hello' | uppercase }} <!-- HELLO -->
|
||||
{{ name | uppercase }} <!-- JOHN DOE -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### LowerCasePipe
|
||||
|
||||
Konwertuje tekst na małe litery.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'lowercase' })
|
||||
```
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ 'HELLO' | lowercase }} <!-- hello -->
|
||||
{{ name | lowercase }} <!-- john doe -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### JsonPipe
|
||||
|
||||
Serializuje obiekt do formatu JSON z wcięciami.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'json' })
|
||||
```
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ user | json }}
|
||||
<!--
|
||||
{
|
||||
"name": "John",
|
||||
"age": 30
|
||||
}
|
||||
-->
|
||||
|
||||
{{ items | json }}
|
||||
<!--
|
||||
[
|
||||
"item1",
|
||||
"item2"
|
||||
]
|
||||
-->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CamelCasePipe
|
||||
|
||||
Konwertuje tekst do camelCase.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'camelcase' })
|
||||
```
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ 'hello-world' | camelcase }} <!-- helloWorld -->
|
||||
{{ 'hello_world' | camelcase }} <!-- helloWorld -->
|
||||
{{ 'hello world' | camelcase }} <!-- helloWorld -->
|
||||
{{ 'HelloWorld' | camelcase }} <!-- helloWorld -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PascalCasePipe
|
||||
|
||||
Konwertuje tekst do PascalCase.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'pascalcase' })
|
||||
```
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ 'hello-world' | pascalcase }} <!-- HelloWorld -->
|
||||
{{ 'hello_world' | pascalcase }} <!-- HelloWorld -->
|
||||
{{ 'hello world' | pascalcase }} <!-- HelloWorld -->
|
||||
{{ 'helloWorld' | pascalcase }} <!-- HelloWorld -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### SnakeCasePipe
|
||||
|
||||
Konwertuje tekst do snake_case.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'snakecase' })
|
||||
```
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ 'helloWorld' | snakecase }} <!-- hello_world -->
|
||||
{{ 'HelloWorld' | snakecase }} <!-- hello_world -->
|
||||
{{ 'hello-world' | snakecase }} <!-- hello_world -->
|
||||
{{ 'hello world' | snakecase }} <!-- hello_world -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### KebabCasePipe
|
||||
|
||||
Konwertuje tekst do kebab-case.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'kebabcase' })
|
||||
```
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ 'helloWorld' | kebabcase }} <!-- hello-world -->
|
||||
{{ 'HelloWorld' | kebabcase }} <!-- hello-world -->
|
||||
{{ 'hello_world' | kebabcase }} <!-- hello-world -->
|
||||
{{ 'hello world' | kebabcase }} <!-- hello-world -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### SubstrPipe
|
||||
|
||||
Zwraca fragment tekstu.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'substr' })
|
||||
```
|
||||
|
||||
**Parametry:**
|
||||
- `start: number` - pozycja początkowa
|
||||
- `length?: number` - długość fragmentu (opcjonalne)
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ 'hello world' | substr:0:5 }} <!-- hello -->
|
||||
{{ 'hello world' | substr:6 }} <!-- world -->
|
||||
{{ text | substr:0:10 }} <!-- pierwsze 10 znaków -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DatePipe
|
||||
|
||||
Formatuje daty.
|
||||
|
||||
```typescript
|
||||
@Pipe({ name: 'date' })
|
||||
```
|
||||
|
||||
**Parametry:**
|
||||
- `format: string` - format daty (domyślnie: 'medium')
|
||||
|
||||
**Predefiniowane formaty:**
|
||||
|
||||
| Format | Przykład |
|
||||
|--------|----------|
|
||||
| `short` | 1/15/24, 2:30 PM |
|
||||
| `medium` | Jan 15, 2024, 2:30:45 PM |
|
||||
| `long` | January 15, 2024 at 2:30:45 PM |
|
||||
| `full` | Monday, January 15, 2024 at 2:30:45 PM |
|
||||
| `shortDate` | 1/15/24 |
|
||||
| `mediumDate` | Jan 15, 2024 |
|
||||
| `longDate` | January 15, 2024 |
|
||||
| `fullDate` | Monday, January 15, 2024 |
|
||||
| `shortTime` | 2:30 PM |
|
||||
| `mediumTime` | 2:30:45 PM |
|
||||
|
||||
**Własne formaty:**
|
||||
|
||||
| Symbol | Znaczenie | Przykład |
|
||||
|--------|-----------|----------|
|
||||
| `yyyy` | Rok (4 cyfry) | 2024 |
|
||||
| `yy` | Rok (2 cyfry) | 24 |
|
||||
| `MM` | Miesiąc (2 cyfry) | 01 |
|
||||
| `M` | Miesiąc (1-2 cyfry) | 1 |
|
||||
| `dd` | Dzień (2 cyfry) | 15 |
|
||||
| `d` | Dzień (1-2 cyfry) | 15 |
|
||||
| `HH` | Godzina 24h (2 cyfry) | 14 |
|
||||
| `H` | Godzina 24h (1-2 cyfry) | 14 |
|
||||
| `hh` | Godzina 12h (2 cyfry) | 02 |
|
||||
| `h` | Godzina 12h (1-2 cyfry) | 2 |
|
||||
| `mm` | Minuty (2 cyfry) | 30 |
|
||||
| `m` | Minuty (1-2 cyfry) | 30 |
|
||||
| `ss` | Sekundy (2 cyfry) | 45 |
|
||||
| `s` | Sekundy (1-2 cyfry) | 45 |
|
||||
| `a` | AM/PM | PM |
|
||||
|
||||
**Przykłady:**
|
||||
```html
|
||||
{{ date | date }} <!-- Jan 15, 2024, 2:30:45 PM -->
|
||||
{{ date | date:'short' }} <!-- 1/15/24, 2:30 PM -->
|
||||
{{ date | date:'yyyy-MM-dd' }} <!-- 2024-01-15 -->
|
||||
{{ date | date:'HH:mm:ss' }} <!-- 14:30:45 -->
|
||||
{{ date | date:'dd/MM/yyyy' }} <!-- 15/01/2024 -->
|
||||
{{ date | date:'h:mm a' }} <!-- 2:30 PM -->
|
||||
```
|
||||
|
||||
## Łańcuchowanie Pipes
|
||||
|
||||
Możesz łączyć wiele pipes w łańcuch:
|
||||
|
||||
```html
|
||||
{{ name | lowercase | camelcase }}
|
||||
{{ text | substr:0:20 | uppercase }}
|
||||
{{ value | json | lowercase }}
|
||||
```
|
||||
|
||||
## Kombinacja z operatorami
|
||||
|
||||
Pipes działają poprawnie z operatorami logicznymi:
|
||||
|
||||
```html
|
||||
{{ value || 'default' | uppercase }}
|
||||
{{ (name || 'Unknown') | pascalcase }}
|
||||
{{ condition && value | lowercase }}
|
||||
```
|
||||
|
||||
## Tworzenie własnych Pipes
|
||||
|
||||
```typescript
|
||||
import { Pipe, PipeTransform } from '@quarc/core';
|
||||
|
||||
@Pipe({ name: 'reverse' })
|
||||
export class ReversePipe implements PipeTransform {
|
||||
transform(value: string | null | undefined): string {
|
||||
if (value == null) return '';
|
||||
return String(value).split('').reverse().join('');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Użycie:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-example',
|
||||
template: '<div>{{ text | reverse }}</div>',
|
||||
imports: [ReversePipe],
|
||||
})
|
||||
export class ExampleComponent {
|
||||
text = 'hello'; // Wyświetli: olleh
|
||||
}
|
||||
```
|
||||
|
||||
## Testy
|
||||
|
||||
Wszystkie pipes są przetestowane. Uruchom testy:
|
||||
|
||||
```bash
|
||||
cd /web/quarc/tests/unit
|
||||
npx ts-node test-pipes.ts
|
||||
```
|
||||
|
||||
## Uwagi
|
||||
|
||||
- Wszystkie pipes obsługują wartości `null` i `undefined` zwracając pusty string
|
||||
- DatePipe obsługuje obiekty `Date`, stringi i liczby (timestamp)
|
||||
- Pipes są transformowane w czasie kompilacji na wywołania metod dla minimalnego rozmiaru bundle
|
||||
- Pipes są pure (czyste) - wynik zależy tylko od argumentów wejściowych
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'camelcase' })
|
||||
export class CamelCasePipe implements PipeTransform {
|
||||
transform(value: string | null | undefined): string {
|
||||
if (value == null) return '';
|
||||
|
||||
return String(value)
|
||||
.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : '')
|
||||
.replace(/^[A-Z]/, char => char.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'date' })
|
||||
export class DatePipe implements PipeTransform {
|
||||
transform(value: Date | string | number | null | undefined, format: string = 'medium'): string {
|
||||
if (value == null) return '';
|
||||
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case 'short':
|
||||
return this.formatShort(date);
|
||||
case 'medium':
|
||||
return this.formatMedium(date);
|
||||
case 'long':
|
||||
return this.formatLong(date);
|
||||
case 'full':
|
||||
return this.formatFull(date);
|
||||
case 'shortDate':
|
||||
return this.formatShortDate(date);
|
||||
case 'mediumDate':
|
||||
return this.formatMediumDate(date);
|
||||
case 'longDate':
|
||||
return this.formatLongDate(date);
|
||||
case 'fullDate':
|
||||
return this.formatFullDate(date);
|
||||
case 'shortTime':
|
||||
return this.formatShortTime(date);
|
||||
case 'mediumTime':
|
||||
return this.formatMediumTime(date);
|
||||
default:
|
||||
return this.formatCustom(date, format);
|
||||
}
|
||||
}
|
||||
|
||||
private pad(num: number, size: number = 2): string {
|
||||
return String(num).padStart(size, '0');
|
||||
}
|
||||
|
||||
private formatShort(date: Date): string {
|
||||
return `${this.pad(date.getMonth() + 1)}/${this.pad(date.getDate())}/${date.getFullYear().toString().substr(2)}, ${this.formatShortTime(date)}`;
|
||||
}
|
||||
|
||||
private formatMedium(date: Date): string {
|
||||
return `${this.getMonthShort(date)} ${date.getDate()}, ${date.getFullYear()}, ${this.formatMediumTime(date)}`;
|
||||
}
|
||||
|
||||
private formatLong(date: Date): string {
|
||||
return `${this.getMonthLong(date)} ${date.getDate()}, ${date.getFullYear()} at ${this.formatMediumTime(date)}`;
|
||||
}
|
||||
|
||||
private formatFull(date: Date): string {
|
||||
return `${this.getDayLong(date)}, ${this.getMonthLong(date)} ${date.getDate()}, ${date.getFullYear()} at ${this.formatMediumTime(date)}`;
|
||||
}
|
||||
|
||||
private formatShortDate(date: Date): string {
|
||||
return `${this.pad(date.getMonth() + 1)}/${this.pad(date.getDate())}/${date.getFullYear().toString().substr(2)}`;
|
||||
}
|
||||
|
||||
private formatMediumDate(date: Date): string {
|
||||
return `${this.getMonthShort(date)} ${date.getDate()}, ${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
private formatLongDate(date: Date): string {
|
||||
return `${this.getMonthLong(date)} ${date.getDate()}, ${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
private formatFullDate(date: Date): string {
|
||||
return `${this.getDayLong(date)}, ${this.getMonthLong(date)} ${date.getDate()}, ${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
private formatShortTime(date: Date): string {
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
const displayHours = hours % 12 || 12;
|
||||
return `${displayHours}:${this.pad(minutes)} ${ampm}`;
|
||||
}
|
||||
|
||||
private formatMediumTime(date: Date): string {
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
const seconds = date.getSeconds();
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
const displayHours = hours % 12 || 12;
|
||||
return `${displayHours}:${this.pad(minutes)}:${this.pad(seconds)} ${ampm}`;
|
||||
}
|
||||
|
||||
private getMonthShort(date: Date): string {
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
return months[date.getMonth()];
|
||||
}
|
||||
|
||||
private getMonthLong(date: Date): string {
|
||||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
return months[date.getMonth()];
|
||||
}
|
||||
|
||||
private getDayLong(date: Date): string {
|
||||
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
return days[date.getDay()];
|
||||
}
|
||||
|
||||
private formatCustom(date: Date, format: string): string {
|
||||
return format
|
||||
.replace(/yyyy/g, String(date.getFullYear()))
|
||||
.replace(/yy/g, String(date.getFullYear()).substr(2))
|
||||
.replace(/MM/g, this.pad(date.getMonth() + 1))
|
||||
.replace(/M/g, String(date.getMonth() + 1))
|
||||
.replace(/dd/g, this.pad(date.getDate()))
|
||||
.replace(/d/g, String(date.getDate()))
|
||||
.replace(/HH/g, this.pad(date.getHours()))
|
||||
.replace(/H/g, String(date.getHours()))
|
||||
.replace(/hh/g, this.pad(date.getHours() % 12 || 12))
|
||||
.replace(/h/g, String(date.getHours() % 12 || 12))
|
||||
.replace(/mm/g, this.pad(date.getMinutes()))
|
||||
.replace(/m/g, String(date.getMinutes()))
|
||||
.replace(/ss/g, this.pad(date.getSeconds()))
|
||||
.replace(/s/g, String(date.getSeconds()))
|
||||
.replace(/a/g, date.getHours() >= 12 ? 'PM' : 'AM');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export { UpperCasePipe } from './uppercase.pipe';
|
||||
export { LowerCasePipe } from './lowercase.pipe';
|
||||
export { JsonPipe } from './json.pipe';
|
||||
export { CamelCasePipe } from './camelcase.pipe';
|
||||
export { PascalCasePipe } from './pascalcase.pipe';
|
||||
export { SnakeCasePipe } from './snakecase.pipe';
|
||||
export { KebabCasePipe } from './kebabcase.pipe';
|
||||
export { SubstrPipe } from './substr.pipe';
|
||||
export { DatePipe } from './date.pipe';
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'json' })
|
||||
export class JsonPipe implements PipeTransform {
|
||||
transform(value: any): string {
|
||||
try {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} catch (e) {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'kebabcase' })
|
||||
export class KebabCasePipe implements PipeTransform {
|
||||
transform(value: string | null | undefined): string {
|
||||
if (value == null) return '';
|
||||
|
||||
return String(value)
|
||||
.replace(/([A-Z])/g, '-$1')
|
||||
.replace(/[_\s]+/g, '-')
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'lowercase' })
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string | null | undefined): string {
|
||||
if (value == null) return '';
|
||||
return String(value).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'pascalcase' })
|
||||
export class PascalCasePipe implements PipeTransform {
|
||||
transform(value: string | null | undefined): string {
|
||||
if (value == null) return '';
|
||||
|
||||
return String(value)
|
||||
.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : '')
|
||||
.replace(/^[a-z]/, char => char.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'snakecase' })
|
||||
export class SnakeCasePipe implements PipeTransform {
|
||||
transform(value: string | null | undefined): string {
|
||||
if (value == null) return '';
|
||||
|
||||
return String(value)
|
||||
.replace(/([A-Z])/g, '_$1')
|
||||
.replace(/[-\s]+/g, '_')
|
||||
.replace(/^_/, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'substr' })
|
||||
export class SubstrPipe implements PipeTransform {
|
||||
transform(value: string | null | undefined, start: number, length?: number): string {
|
||||
if (value == null) return '';
|
||||
|
||||
const str = String(value);
|
||||
|
||||
if (length !== undefined) {
|
||||
return str.substr(start, length);
|
||||
}
|
||||
|
||||
return str.substr(start);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Pipe, PipeTransform } from '../angular/pipe';
|
||||
|
||||
@Pipe({ name: 'uppercase' })
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string | null | undefined): string {
|
||||
if (value == null) return '';
|
||||
return String(value).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
# Quarc E2E Pipes Tests
|
||||
|
||||
Prawdziwe testy end-to-end dla wszystkich pipes w frameworku Quarc.
|
||||
|
||||
## Opis
|
||||
|
||||
Testy uruchamiają prawdziwą aplikację Quarc z routingiem, gdzie każda strona testuje inny pipe lub grupę pipes. Serwer deweloperski jest uruchamiany na losowym porcie, aby uniknąć konfliktów.
|
||||
|
||||
## Struktura
|
||||
|
||||
```
|
||||
e2e/
|
||||
├── app/ # Aplikacja testowa
|
||||
│ ├── pages/ # Komponenty testowe dla każdego pipe
|
||||
│ │ ├── home.component.ts
|
||||
│ │ ├── uppercase-test.component.ts
|
||||
│ │ ├── lowercase-test.component.ts
|
||||
│ │ ├── json-test.component.ts
|
||||
│ │ ├── case-test.component.ts
|
||||
│ │ ├── date-test.component.ts
|
||||
│ │ ├── substr-test.component.ts
|
||||
│ │ └── chain-test.component.ts
|
||||
│ ├── app.component.ts # Root component z nawigacją
|
||||
│ ├── routes.ts # Routing configuration
|
||||
│ ├── main.ts # Entry point
|
||||
│ ├── index.html # HTML template
|
||||
│ └── quarc.json # Quarc config
|
||||
├── run-e2e-tests.ts # Test runner
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Testowane Pipes
|
||||
|
||||
### 1. UpperCasePipe (`/uppercase`)
|
||||
- Hardcoded string
|
||||
- Signal value
|
||||
- Method call
|
||||
- Z operatorem `||`
|
||||
|
||||
### 2. LowerCasePipe (`/lowercase`)
|
||||
- Hardcoded string
|
||||
- Signal value
|
||||
- Method call
|
||||
|
||||
### 3. JsonPipe (`/json`)
|
||||
- Number literal (123)
|
||||
- String literal ("string")
|
||||
- Boolean literal (true)
|
||||
- Object z signal
|
||||
- Array z signal
|
||||
- Object z method
|
||||
|
||||
### 4. Case Pipes (`/case`)
|
||||
- CamelCasePipe
|
||||
- PascalCasePipe
|
||||
- SnakeCasePipe
|
||||
- KebabCasePipe
|
||||
- Z signal values
|
||||
|
||||
### 5. DatePipe (`/date`)
|
||||
- Custom format `yyyy-MM-dd`
|
||||
- Custom format `HH:mm:ss`
|
||||
- Predefined format `shortDate`
|
||||
- Z method call
|
||||
|
||||
### 6. SubstrPipe (`/substr`)
|
||||
- Z start i length
|
||||
- Z start only
|
||||
- Signal value
|
||||
- Method call
|
||||
|
||||
### 7. Pipe Chain (`/chain`)
|
||||
- lowercase | uppercase
|
||||
- uppercase | substr
|
||||
- Signal z chain
|
||||
- Method z chain
|
||||
- Triple chain
|
||||
|
||||
## Struktura projektów
|
||||
|
||||
Testy e2e składają się z dwóch osobnych projektów:
|
||||
|
||||
1. **`/web/quarc/tests/e2e`** - główny projekt testowy
|
||||
- Zawiera runner testów (`run-e2e-tests.ts`)
|
||||
- `postinstall`: automatycznie instaluje zależności w `app/`
|
||||
- `preserve`: zapewnia że `app/` ma zainstalowane zależności przed serve
|
||||
|
||||
2. **`/web/quarc/tests/e2e/app`** - aplikacja testowa
|
||||
- Zawiera komponenty testujące wszystkie pipes
|
||||
- Ma własne `package.json` z zależnościami (typescript, ts-node, @types/node)
|
||||
- Build: `npm run build`
|
||||
- Serve: `npm run serve` (instaluje zależności i uruchamia dev server)
|
||||
|
||||
## Uruchomienie
|
||||
|
||||
```bash
|
||||
cd /web/quarc/tests/e2e
|
||||
npm install # Zainstaluje zależności w e2e/ i automatycznie w app/
|
||||
npm test # Zbuduje app, uruchomi serwer i wykona testy
|
||||
```
|
||||
|
||||
## Jak to działa
|
||||
|
||||
1. **Start serwera**: Uruchamia `qu serve` na losowym porcie (3000-8000)
|
||||
2. **Czekanie**: Odczytuje output serwera i czeka aż będzie nasłuchiwał
|
||||
3. **Testy**: Dla każdego route:
|
||||
- Pobiera HTML strony
|
||||
- Parsuje wyniki testów (porównuje `.result` z `.expected`)
|
||||
- Zapisuje wyniki
|
||||
4. **Raport**: Wyświetla podsumowanie wszystkich testów
|
||||
5. **Cleanup**: Zamyka serwer deweloperski
|
||||
|
||||
## Przykładowy output
|
||||
|
||||
```
|
||||
🧪 Starting E2E Pipes Test Suite
|
||||
|
||||
🚀 Starting dev server on port 4523...
|
||||
✓ Server started at http://localhost:4523
|
||||
⏳ Waiting for server to be ready...
|
||||
✓ Server is ready
|
||||
|
||||
📋 Testing: UpperCase Pipe (/uppercase)
|
||||
✓ test-1: PASS
|
||||
✓ test-2: PASS
|
||||
✓ test-3: PASS
|
||||
✓ test-4: PASS
|
||||
|
||||
📋 Testing: JSON Pipe (/json)
|
||||
✓ test-1: PASS
|
||||
✓ test-2: PASS
|
||||
✓ test-3: PASS
|
||||
✓ test-4: PASS
|
||||
✓ test-5: PASS
|
||||
✓ test-6: PASS
|
||||
|
||||
...
|
||||
|
||||
============================================================
|
||||
📊 E2E TEST RESULTS
|
||||
============================================================
|
||||
✓ /uppercase: 4/4 passed
|
||||
✓ /lowercase: 3/3 passed
|
||||
✓ /json: 6/6 passed
|
||||
✓ /case: 5/5 passed
|
||||
✓ /date: 4/4 passed
|
||||
✓ /substr: 4/4 passed
|
||||
✓ /chain: 5/5 passed
|
||||
============================================================
|
||||
Total: 31/31 tests passed
|
||||
Success rate: 100.0%
|
||||
|
||||
✅ All E2E tests passed!
|
||||
|
||||
🛑 Stopping dev server...
|
||||
```
|
||||
|
||||
## Debugowanie
|
||||
|
||||
Jeśli testy nie przechodzą:
|
||||
|
||||
1. Uruchom aplikację manualnie:
|
||||
```bash
|
||||
cd app
|
||||
../../../cli/bin/qu.js serve
|
||||
```
|
||||
|
||||
2. Otwórz w przeglądarce i sprawdź każdy route
|
||||
|
||||
3. Sprawdź console w DevTools
|
||||
|
||||
4. Porównaj `.result` z `.expected` wizualnie
|
||||
|
||||
## Uwagi
|
||||
|
||||
- Testy używają `fetch()` do pobierania HTML, więc wymagają Node.js 18+
|
||||
- Serwer jest uruchamiany na losowym porcie aby uniknąć konfliktów
|
||||
- Każdy test czeka 1s po nawigacji aby komponent się wyrenderował
|
||||
- Testy porównują znormalizowany tekst (bez whitespace dla JSON)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Quarc E2E Pipes Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
padding: 20px;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
nav {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
background: #252526;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
nav a {
|
||||
color: #4ec9b0;
|
||||
margin-right: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.test {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
border: 1px solid #444;
|
||||
background: #252526;
|
||||
}
|
||||
.test h3 {
|
||||
margin-top: 0;
|
||||
color: #4ec9b0;
|
||||
}
|
||||
.result {
|
||||
padding: 10px;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #555;
|
||||
margin: 5px 0;
|
||||
color: #ce9178;
|
||||
}
|
||||
.expected, .expected-pattern {
|
||||
padding: 10px;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #555;
|
||||
margin: 5px 0;
|
||||
color: #6a9955;
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "quarc-e2e-test-app",
|
||||
"version": "1.0.0",
|
||||
"description": "Test application for Quarc E2E pipes tests",
|
||||
"scripts": {
|
||||
"build": "node ../../../cli/bin/qu.js build",
|
||||
"serve": "npm install && node ../../../cli/bin/qu.js serve",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"environment": "production",
|
||||
"build": {
|
||||
"actions": {
|
||||
"prebuild": [],
|
||||
"postbuild": []
|
||||
},
|
||||
"minifyNames": true,
|
||||
"scripts": [],
|
||||
"externalEntryPoints": [],
|
||||
"styles": [],
|
||||
"externalStyles": [],
|
||||
"limits": {
|
||||
"total": {
|
||||
"warning": "100 KB",
|
||||
"error": "500 KB"
|
||||
},
|
||||
"main": {
|
||||
"warning": "50 KB",
|
||||
"error": "150 KB"
|
||||
},
|
||||
"sourceMaps": {
|
||||
"warning": "200 KB",
|
||||
"error": "500 KB"
|
||||
},
|
||||
"components": {
|
||||
"warning": "10 KB",
|
||||
"error": "50 KB"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"actions": {
|
||||
"preserve": [],
|
||||
"postserve": []
|
||||
},
|
||||
"staticPaths": [],
|
||||
"proxy": {}
|
||||
},
|
||||
"environments": {
|
||||
"development": {
|
||||
"treatWarningsAsErrors": false,
|
||||
"minifyNames": false,
|
||||
"generateSourceMaps": true,
|
||||
"compressed": false,
|
||||
"devServer": {
|
||||
"port": 4200
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"treatWarningsAsErrors": false,
|
||||
"minifyNames": false,
|
||||
"generateSourceMaps": false,
|
||||
"compressed": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { Component } from '../../../../core/index';
|
||||
import { RouterOutlet } from '../../../../router/index';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<nav>
|
||||
<a href="/">Home</a> |
|
||||
<a href="/uppercase">UpperCase</a> |
|
||||
<a href="/lowercase">LowerCase</a> |
|
||||
<a href="/json">JSON</a> |
|
||||
<a href="/case">Case</a> |
|
||||
<a href="/date">Date</a> |
|
||||
<a href="/substr">Substr</a> |
|
||||
<a href="/chain">Chain</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
imports: [RouterOutlet],
|
||||
})
|
||||
export class AppComponent {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { bootstrapApplication } from '../../../../platform-browser/browser';
|
||||
import { ApplicationConfig } from '../../../../core/index';
|
||||
import { provideRouter } from '../../../../router/index';
|
||||
import { AppComponent } from './app.component';
|
||||
import { routes } from './routes';
|
||||
|
||||
const appConfig: ApplicationConfig = {
|
||||
providers: [provideRouter(routes)],
|
||||
};
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig);
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { Component, signal } from '../../../../../core/index';
|
||||
import { CamelCasePipe } from '../../../../../core/pipes/camelcase.pipe';
|
||||
import { PascalCasePipe } from '../../../../../core/pipes/pascalcase.pipe';
|
||||
import { SnakeCasePipe } from '../../../../../core/pipes/snakecase.pipe';
|
||||
import { KebabCasePipe } from '../../../../../core/pipes/kebabcase.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-case-test',
|
||||
template: `
|
||||
<h2>Case Pipes Test</h2>
|
||||
|
||||
<div class="test" id="test-1">
|
||||
<h3>Test 1: CamelCase</h3>
|
||||
<div class="result">{{ 'hello-world' | camelcase }}</div>
|
||||
<div class="expected">helloWorld</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-2">
|
||||
<h3>Test 2: PascalCase</h3>
|
||||
<div class="result">{{ 'hello-world' | pascalcase }}</div>
|
||||
<div class="expected">HelloWorld</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-3">
|
||||
<h3>Test 3: SnakeCase</h3>
|
||||
<div class="result">{{ 'helloWorld' | snakecase }}</div>
|
||||
<div class="expected">hello_world</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-4">
|
||||
<h3>Test 4: KebabCase</h3>
|
||||
<div class="result">{{ 'helloWorld' | kebabcase }}</div>
|
||||
<div class="expected">hello-world</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-5">
|
||||
<h3>Test 5: CamelCase from signal</h3>
|
||||
<div class="result">{{ text() | camelcase }}</div>
|
||||
<div class="expected">testValue</div>
|
||||
</div>
|
||||
`,
|
||||
imports: [CamelCasePipe, PascalCasePipe, SnakeCasePipe, KebabCasePipe],
|
||||
})
|
||||
export class CaseTestComponent {
|
||||
text = signal('test-value');
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { Component, signal } from '../../../../../core/index';
|
||||
import { UpperCasePipe } from '../../../../../core/pipes/uppercase.pipe';
|
||||
import { LowerCasePipe } from '../../../../../core/pipes/lowercase.pipe';
|
||||
import { SubstrPipe } from '../../../../../core/pipes/substr.pipe';
|
||||
import { CamelCasePipe } from '../../../../../core/pipes/camelcase.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chain-test',
|
||||
template: `
|
||||
<h2>Pipe Chain Test</h2>
|
||||
|
||||
<div class="test" id="test-1">
|
||||
<h3>Test 1: lowercase | uppercase</h3>
|
||||
<div class="result">{{ 'Hello' | lowercase | uppercase }}</div>
|
||||
<div class="expected">HELLO</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-2">
|
||||
<h3>Test 2: uppercase | substr</h3>
|
||||
<div class="result">{{ 'hello world' | uppercase | substr:0:5 }}</div>
|
||||
<div class="expected">HELLO</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-3">
|
||||
<h3>Test 3: Signal with chain</h3>
|
||||
<div class="result">{{ text() | lowercase | camelcase }}</div>
|
||||
<div class="expected">helloWorld</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-4">
|
||||
<h3>Test 4: Method with chain</h3>
|
||||
<div class="result">{{ getText() | uppercase | substr:0:4 }}</div>
|
||||
<div class="expected">TEST</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-5">
|
||||
<h3>Test 5: Triple chain</h3>
|
||||
<div class="result">{{ 'HELLO-WORLD' | lowercase | camelcase | uppercase }}</div>
|
||||
<div class="expected">HELLOWORLD</div>
|
||||
</div>
|
||||
`,
|
||||
imports: [UpperCasePipe, LowerCasePipe, SubstrPipe, CamelCasePipe],
|
||||
})
|
||||
export class ChainTestComponent {
|
||||
text = signal('HELLO-WORLD');
|
||||
|
||||
getText() {
|
||||
return 'test value';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { Component, signal } from '../../../../../core/index';
|
||||
import { DatePipe } from '../../../../../core/pipes/date.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-date-test',
|
||||
template: `
|
||||
<h2>Date Pipe Test</h2>
|
||||
|
||||
<div class="test" id="test-1">
|
||||
<h3>Test 1: Custom format yyyy-MM-dd</h3>
|
||||
<div class="result">{{ date() | date:'yyyy-MM-dd' }}</div>
|
||||
<div class="expected">2024-01-15</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-2">
|
||||
<h3>Test 2: Custom format HH:mm:ss</h3>
|
||||
<div class="result">{{ date() | date:'HH:mm:ss' }}</div>
|
||||
<div class="expected">14:30:45</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-3">
|
||||
<h3>Test 3: Short date</h3>
|
||||
<div class="result">{{ date() | date:'shortDate' }}</div>
|
||||
<div class="expected-pattern">01/15/24</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-4">
|
||||
<h3>Test 4: From method</h3>
|
||||
<div class="result">{{ getDate() | date:'yyyy-MM-dd' }}</div>
|
||||
<div class="expected">2024-01-15</div>
|
||||
</div>
|
||||
`,
|
||||
imports: [DatePipe],
|
||||
})
|
||||
export class DateTestComponent {
|
||||
date = signal(new Date('2024-01-15T14:30:45'));
|
||||
|
||||
getDate() {
|
||||
return new Date('2024-01-15T14:30:45');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Component } from '../../../../../core/index';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
template: `
|
||||
<h1>E2E Pipes Test Suite</h1>
|
||||
<p>Navigate to test different pipes</p>
|
||||
<div id="test-status">ready</div>
|
||||
`,
|
||||
})
|
||||
export class HomeComponent {}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { Component, signal } from '../../../../../core/index';
|
||||
import { JsonPipe } from '../../../../../core/pipes/json.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-json-test',
|
||||
template: `
|
||||
<h2>JSON Pipe Test</h2>
|
||||
|
||||
<div class="test" id="test-1">
|
||||
<h3>Test 1: Number literal</h3>
|
||||
<pre class="result">{{ 123 | json }}</pre>
|
||||
<pre class="expected">123</pre>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-2">
|
||||
<h3>Test 2: String literal</h3>
|
||||
<pre class="result">{{ "string" | json }}</pre>
|
||||
<pre class="expected">"string"</pre>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-3">
|
||||
<h3>Test 3: Boolean literal</h3>
|
||||
<pre class="result">{{ true | json }}</pre>
|
||||
<pre class="expected">true</pre>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-4">
|
||||
<h3>Test 4: Object from signal</h3>
|
||||
<pre class="result">{{ obj() | json }}</pre>
|
||||
<pre class="expected">{"name":"Test","value":123}</pre>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-5">
|
||||
<h3>Test 5: Array from signal</h3>
|
||||
<pre class="result">{{ arr() | json }}</pre>
|
||||
<pre class="expected">[1,2,3]</pre>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-6">
|
||||
<h3>Test 6: Object from method</h3>
|
||||
<pre class="result">{{ getObject() | json }}</pre>
|
||||
<pre class="expected">{"method":true}</pre>
|
||||
</div>
|
||||
`,
|
||||
imports: [JsonPipe],
|
||||
})
|
||||
export class JsonTestComponent {
|
||||
obj = signal({ name: 'Test', value: 123 });
|
||||
arr = signal([1, 2, 3]);
|
||||
|
||||
getObject() {
|
||||
return { method: true };
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { Component, signal } from '../../../../../core/index';
|
||||
import { LowerCasePipe } from '../../../../../core/pipes/lowercase.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lowercase-test',
|
||||
template: `
|
||||
<h2>LowerCase Pipe Test</h2>
|
||||
|
||||
<div class="test" id="test-1">
|
||||
<h3>Test 1: Hardcoded string</h3>
|
||||
<div class="result">{{ 'HELLO WORLD' | lowercase }}</div>
|
||||
<div class="expected">hello world</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-2">
|
||||
<h3>Test 2: Signal value</h3>
|
||||
<div class="result">{{ text() | lowercase }}</div>
|
||||
<div class="expected">quarc framework</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-3">
|
||||
<h3>Test 3: Method call</h3>
|
||||
<div class="result">{{ getText() | lowercase }}</div>
|
||||
<div class="expected">from method</div>
|
||||
</div>
|
||||
`,
|
||||
imports: [LowerCasePipe],
|
||||
})
|
||||
export class LowerCaseTestComponent {
|
||||
text = signal('QUARC FRAMEWORK');
|
||||
|
||||
getText() {
|
||||
return 'FROM METHOD';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { Component, signal } from '../../../../../core/index';
|
||||
import { SubstrPipe } from '../../../../../core/pipes/substr.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-substr-test',
|
||||
template: `
|
||||
<h2>Substr Pipe Test</h2>
|
||||
|
||||
<div class="test" id="test-1">
|
||||
<h3>Test 1: Hardcoded with start and length</h3>
|
||||
<div class="result">{{ 'hello world' | substr:0:5 }}</div>
|
||||
<div class="expected">hello</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-2">
|
||||
<h3>Test 2: Hardcoded with start only</h3>
|
||||
<div class="result">{{ 'hello world' | substr:6 }}</div>
|
||||
<div class="expected">world</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-3">
|
||||
<h3>Test 3: Signal value</h3>
|
||||
<div class="result">{{ text() | substr:0:10 }}</div>
|
||||
<div class="expected">quarc fram</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-4">
|
||||
<h3>Test 4: Method call</h3>
|
||||
<div class="result">{{ getText() | substr:5:6 }}</div>
|
||||
<div class="expected">method</div>
|
||||
</div>
|
||||
`,
|
||||
imports: [SubstrPipe],
|
||||
})
|
||||
export class SubstrTestComponent {
|
||||
text = signal('quarc framework');
|
||||
|
||||
getText() {
|
||||
return 'from method call';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { Component, signal } from '../../../../../core/index';
|
||||
import { UpperCasePipe } from '../../../../../core/pipes/uppercase.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-uppercase-test',
|
||||
template: `
|
||||
<h2>UpperCase Pipe Test</h2>
|
||||
|
||||
<div class="test" id="test-1">
|
||||
<h3>Test 1: Hardcoded string</h3>
|
||||
<div class="result">{{ 'hello world' | uppercase }}</div>
|
||||
<div class="expected">HELLO WORLD</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-2">
|
||||
<h3>Test 2: Signal value</h3>
|
||||
<div class="result">{{ text() | uppercase }}</div>
|
||||
<div class="expected">QUARC FRAMEWORK</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-3">
|
||||
<h3>Test 3: Method call</h3>
|
||||
<div class="result">{{ getText() | uppercase }}</div>
|
||||
<div class="expected">FROM METHOD</div>
|
||||
</div>
|
||||
|
||||
<div class="test" id="test-4">
|
||||
<h3>Test 4: With || operator</h3>
|
||||
<div class="result">{{ nullValue() || 'default' | uppercase }}</div>
|
||||
<div class="expected">DEFAULT</div>
|
||||
</div>
|
||||
`,
|
||||
imports: [UpperCasePipe],
|
||||
})
|
||||
export class UpperCaseTestComponent {
|
||||
text = signal('quarc framework');
|
||||
nullValue = signal(null);
|
||||
|
||||
getText() {
|
||||
return 'from method';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Quarc E2E Pipes Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
padding: 20px;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
nav {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
background: #252526;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
nav a {
|
||||
color: #4ec9b0;
|
||||
margin-right: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.test {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
border: 1px solid #444;
|
||||
background: #252526;
|
||||
}
|
||||
.test h3 {
|
||||
margin-top: 0;
|
||||
color: #4ec9b0;
|
||||
}
|
||||
.result {
|
||||
padding: 10px;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #555;
|
||||
margin: 5px 0;
|
||||
color: #ce9178;
|
||||
}
|
||||
.expected, .expected-pattern {
|
||||
padding: 10px;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #555;
|
||||
margin: 5px 0;
|
||||
color: #6a9955;
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { Routes } from '../../../../router/index';
|
||||
import { HomeComponent } from './pages/home.component';
|
||||
import { UpperCaseTestComponent } from './pages/uppercase-test.component';
|
||||
import { LowerCaseTestComponent } from './pages/lowercase-test.component';
|
||||
import { JsonTestComponent } from './pages/json-test.component';
|
||||
import { CaseTestComponent } from './pages/case-test.component';
|
||||
import { DateTestComponent } from './pages/date-test.component';
|
||||
import { SubstrTestComponent } from './pages/substr-test.component';
|
||||
import { ChainTestComponent } from './pages/chain-test.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: HomeComponent },
|
||||
{ path: 'uppercase', component: UpperCaseTestComponent },
|
||||
{ path: 'lowercase', component: LowerCaseTestComponent },
|
||||
{ path: 'json', component: JsonTestComponent },
|
||||
{ path: 'case', component: CaseTestComponent },
|
||||
{ path: 'date', component: DateTestComponent },
|
||||
{ path: 'substr', component: SubstrTestComponent },
|
||||
{ path: 'chain', component: ChainTestComponent },
|
||||
];
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "quarc-e2e-tests",
|
||||
"version": "1.0.0",
|
||||
"description": "E2E tests for Quarc pipes",
|
||||
"scripts": {
|
||||
"postinstall": "cd app && npm install",
|
||||
"preserve": "cd app && npm install",
|
||||
"test": "npx ts-node run-e2e-tests.ts",
|
||||
"test:debug": "npx ts-node run-e2e-tests.ts --inspect"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> quarc-e2e-tests@1.0.0 test
|
||||
> npx ts-node run-e2e-tests.ts
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> quarc-e2e-tests@1.0.0 test
|
||||
> npx ts-node run-e2e-tests.ts
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["*.ts", "app/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test: Pipes Simple</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
padding: 20px;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
.test { margin: 10px 0; padding: 10px; border: 1px solid #444; }
|
||||
.pass { background: #1e3a1e; }
|
||||
.fail { background: #3a1e1e; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test: Pipes - Diagnostyka</h1>
|
||||
<div id="app"></div>
|
||||
<div id="results"></div>
|
||||
|
||||
<script>
|
||||
// Sprawdzenie czy template został przetransformowany
|
||||
console.log('=== Checking transformed template ===');
|
||||
|
||||
// Symulacja tego co powinno być w przetransformowanym template
|
||||
const testTemplate = `<span [inner-text]="this._pipes?.['json']?.transform(123)"></span>`;
|
||||
console.log('Expected template:', testTemplate);
|
||||
|
||||
// Symulacja komponentu
|
||||
const component = {
|
||||
_pipes: {
|
||||
json: {
|
||||
transform: (value) => {
|
||||
console.log('JsonPipe.transform called with:', value);
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Symulacja ewaluacji wyrażenia
|
||||
const expr = "this._pipes?.['json']?.transform(123)";
|
||||
console.log('Expression to evaluate:', expr);
|
||||
|
||||
try {
|
||||
const evalFunc = new Function('c', `with(c){return ${expr}}`);
|
||||
const result = evalFunc(component);
|
||||
console.log('Evaluation result:', result);
|
||||
|
||||
document.getElementById('results').innerHTML = `
|
||||
<div class="test ${result ? 'pass' : 'fail'}">
|
||||
<h3>Test 1: Manual evaluation</h3>
|
||||
<div>Expression: ${expr}</div>
|
||||
<div>Result: ${result}</div>
|
||||
<div>Status: ${result ? '✓ PASS' : '✗ FAIL'}</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (e) {
|
||||
console.error('Evaluation error:', e);
|
||||
document.getElementById('results').innerHTML = `
|
||||
<div class="test fail">
|
||||
<h3>Test 1: Manual evaluation</h3>
|
||||
<div>Error: ${e.message}</div>
|
||||
<div>Status: ✗ FAIL</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Test 2: Sprawdzenie czy optional chaining działa
|
||||
console.log('\n=== Test 2: Optional chaining ===');
|
||||
const obj = {};
|
||||
console.log('obj._pipes?.json:', obj._pipes?.['json']);
|
||||
console.log('obj._pipes?.json?.transform:', obj._pipes?.['json']?.transform);
|
||||
|
||||
const obj2 = { _pipes: { json: { transform: (v) => String(v) } } };
|
||||
console.log('obj2._pipes?.json?.transform(123):', obj2._pipes?.['json']?.transform(123));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Szczegółowy test transformacji pipes w template
|
||||
*/
|
||||
|
||||
import { TemplateTransformer } from '../../cli/processors/template/template-transformer';
|
||||
|
||||
console.log('\n=== Detailed Pipe Transformation Test ===\n');
|
||||
|
||||
const transformer = new TemplateTransformer();
|
||||
|
||||
// Test 1: Prosta interpolacja z pipe
|
||||
const test1 = `<div>{{ 123 | json }}</div>`;
|
||||
console.log('Test 1: Simple pipe');
|
||||
console.log('Input:', test1);
|
||||
const result1 = transformer.transformAll(test1);
|
||||
console.log('Output:', result1);
|
||||
console.log('');
|
||||
|
||||
// Sprawdź czy zawiera this._pipes
|
||||
if (result1.includes('this._pipes')) {
|
||||
console.log('✓ Contains this._pipes');
|
||||
|
||||
// Wyciągnij wyrażenie
|
||||
const match = result1.match(/\[inner-text\]="([^"]+)"/);
|
||||
if (match) {
|
||||
console.log('Expression:', match[1]);
|
||||
|
||||
// Sprawdź składnię
|
||||
if (match[1].includes("this._pipes?.['json']?.transform")) {
|
||||
console.log('✓ Correct syntax');
|
||||
} else {
|
||||
console.log('✗ Incorrect syntax');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('✗ Does not contain this._pipes');
|
||||
}
|
||||
|
||||
console.log('\n---\n');
|
||||
|
||||
// Test 2: String z pipe
|
||||
const test2 = `<div>{{ "string" | json }}</div>`;
|
||||
console.log('Test 2: String with pipe');
|
||||
console.log('Input:', test2);
|
||||
const result2 = transformer.transformAll(test2);
|
||||
console.log('Output:', result2);
|
||||
console.log('');
|
||||
|
||||
// Test 3: Boolean z pipe
|
||||
const test3 = `<div>{{ true | json }}</div>`;
|
||||
console.log('Test 3: Boolean with pipe');
|
||||
console.log('Input:', test3);
|
||||
const result3 = transformer.transformAll(test3);
|
||||
console.log('Output:', result3);
|
||||
console.log('');
|
||||
|
||||
// Test 4: Zmienna z pipe
|
||||
const test4 = `<div>{{ value | json }}</div>`;
|
||||
console.log('Test 4: Variable with pipe');
|
||||
console.log('Input:', test4);
|
||||
const result4 = transformer.transformAll(test4);
|
||||
console.log('Output:', result4);
|
||||
console.log('');
|
||||
|
||||
// Test 5: Sprawdzenie czy literały są poprawnie obsługiwane
|
||||
console.log('=== Checking literal handling ===');
|
||||
const literalTests = [
|
||||
{ input: '123', expected: 'number literal' },
|
||||
{ input: '"string"', expected: 'string literal' },
|
||||
{ input: 'true', expected: 'boolean literal' },
|
||||
{ input: 'value', expected: 'variable' },
|
||||
];
|
||||
|
||||
literalTests.forEach(({ input, expected }) => {
|
||||
const template = `{{ ${input} | json }}`;
|
||||
const result = transformer.transformAll(template);
|
||||
const match = result.match(/transform\(([^)]+)\)/);
|
||||
if (match) {
|
||||
console.log(`${expected}: transform(${match[1]})`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\n✅ Detailed transformation test completed');
|
||||
|
|
@ -14,7 +14,7 @@ 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('||');
|
||||
const pass1 = !result1.includes('_pipes?.') && result1.includes('||');
|
||||
console.log('Pass:', pass1);
|
||||
|
||||
// Test 2: Operator && nie powinien być traktowany jako pipe
|
||||
|
|
@ -23,7 +23,7 @@ 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('&&');
|
||||
const pass2 = !result2.includes('_pipes?.') && result2.includes('&&');
|
||||
console.log('Pass:', pass2);
|
||||
|
||||
// Test 3: Prawdziwy pipe powinien być transformowany
|
||||
|
|
@ -32,7 +32,7 @@ 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');
|
||||
const pass3 = result3.includes('_pipes') && result3.includes('uppercase');
|
||||
console.log('Pass:', pass3);
|
||||
|
||||
// Test 4: Pipe z argumentami
|
||||
|
|
@ -41,7 +41,7 @@ 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');
|
||||
const pass4 = result4.includes('_pipes') && result4.includes('slice');
|
||||
console.log('Pass:', pass4);
|
||||
|
||||
// Test 5: Kombinacja || i pipe
|
||||
|
|
@ -50,7 +50,7 @@ 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');
|
||||
const pass5 = result5.includes('_pipes') && result5.includes('||') && result5.includes('uppercase');
|
||||
console.log('Pass:', pass5);
|
||||
|
||||
// Test 6: Wielokrotne ||
|
||||
|
|
@ -59,7 +59,7 @@ 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;
|
||||
const pass6 = !result6.includes('_pipes?.') && (result6.match(/\|\|/g) || []).length === 2;
|
||||
console.log('Pass:', pass6);
|
||||
|
||||
// Test 7: Łańcuch pipes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Test diagnostyczny - sprawdza czy _pipes jest dostępne w komponencie
|
||||
*/
|
||||
|
||||
import { Component, signal } from '../../core/index';
|
||||
import { JsonPipe } from '../../core/pipes/json.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'test-diagnostic',
|
||||
template: '<div>Test</div>',
|
||||
imports: [JsonPipe],
|
||||
})
|
||||
class DiagnosticComponent {
|
||||
value = signal(123);
|
||||
|
||||
constructor() {
|
||||
console.log('DiagnosticComponent constructor');
|
||||
console.log('this._pipes:', (this as any)._pipes);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
console.log('DiagnosticComponent ngOnInit');
|
||||
console.log('this._pipes:', (this as any)._pipes);
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('DiagnosticComponent after timeout');
|
||||
console.log('this._pipes:', (this as any)._pipes);
|
||||
|
||||
if ((this as any)._pipes) {
|
||||
console.log('_pipes keys:', Object.keys((this as any)._pipes));
|
||||
console.log('_pipes.json:', (this as any)._pipes['json']);
|
||||
|
||||
if ((this as any)._pipes['json']) {
|
||||
const result = (this as any)._pipes['json'].transform(123);
|
||||
console.log('Manual pipe call result:', result);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== Diagnostic Test ===\n');
|
||||
|
||||
const comp = new DiagnosticComponent();
|
||||
console.log('After construction, comp._pipes:', (comp as any)._pipes);
|
||||
|
||||
// Symulacja tego co robi WebComponent
|
||||
const pipeInstance = new JsonPipe();
|
||||
(comp as any)._pipes = { json: pipeInstance };
|
||||
|
||||
console.log('After manual assignment, comp._pipes:', (comp as any)._pipes);
|
||||
console.log('Manual transform test:', (comp as any)._pipes.json.transform(123));
|
||||
|
||||
console.log('\n✅ Diagnostic test completed - check logs above');
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test E2E: Pipes</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
padding: 20px;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
.test-section {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
border: 1px solid #444;
|
||||
background: #252526;
|
||||
}
|
||||
.test-section h3 {
|
||||
margin-top: 0;
|
||||
color: #4ec9b0;
|
||||
}
|
||||
.expected {
|
||||
color: #6a9955;
|
||||
}
|
||||
.actual {
|
||||
color: #ce9178;
|
||||
}
|
||||
#test-results {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: #252526;
|
||||
border: 2px solid #444;
|
||||
}
|
||||
.pass { color: #4ec9b0; }
|
||||
.fail { color: #f48771; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test E2E: Quarc Pipes</h1>
|
||||
<div id="app-container"></div>
|
||||
<div id="test-results"></div>
|
||||
|
||||
<script type="module">
|
||||
import { Component, signal, bootstrapApplication } from '../../core/index.js';
|
||||
import {
|
||||
UpperCasePipe,
|
||||
LowerCasePipe,
|
||||
JsonPipe,
|
||||
CamelCasePipe,
|
||||
PascalCasePipe,
|
||||
SnakeCasePipe,
|
||||
KebabCasePipe,
|
||||
SubstrPipe,
|
||||
DatePipe
|
||||
} from '../../core/pipes/index.js';
|
||||
|
||||
@Component({
|
||||
selector: 'test-pipes-app',
|
||||
template: `
|
||||
<div class="test-section">
|
||||
<h3>UpperCasePipe</h3>
|
||||
<div>Input: "hello world"</div>
|
||||
<div class="expected">Expected: HELLO WORLD</div>
|
||||
<div class="actual">Actual: {{ text | uppercase }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>LowerCasePipe</h3>
|
||||
<div>Input: "HELLO WORLD"</div>
|
||||
<div class="expected">Expected: hello world</div>
|
||||
<div class="actual">Actual: {{ upperText | lowercase }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>JsonPipe - Number</h3>
|
||||
<div>Input: 123</div>
|
||||
<div class="expected">Expected: 123</div>
|
||||
<div class="actual">Actual: <pre style="display: inline;">{{ number | json }}</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>JsonPipe - String</h3>
|
||||
<div>Input: "string"</div>
|
||||
<div class="expected">Expected: "string"</div>
|
||||
<div class="actual">Actual: <pre style="display: inline;">{{ str | json }}</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>JsonPipe - Boolean</h3>
|
||||
<div>Input: true</div>
|
||||
<div class="expected">Expected: true</div>
|
||||
<div class="actual">Actual: <pre style="display: inline;">{{ bool | json }}</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>JsonPipe - Object</h3>
|
||||
<div>Input: {name: "Test", value: 123}</div>
|
||||
<div class="expected">Expected: JSON object</div>
|
||||
<div class="actual">Actual: <pre>{{ obj | json }}</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>CamelCasePipe</h3>
|
||||
<div>Input: "hello-world"</div>
|
||||
<div class="expected">Expected: helloWorld</div>
|
||||
<div class="actual">Actual: {{ kebabText | camelcase }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>PascalCasePipe</h3>
|
||||
<div>Input: "hello-world"</div>
|
||||
<div class="expected">Expected: HelloWorld</div>
|
||||
<div class="actual">Actual: {{ kebabText | pascalcase }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>SnakeCasePipe</h3>
|
||||
<div>Input: "helloWorld"</div>
|
||||
<div class="expected">Expected: hello_world</div>
|
||||
<div class="actual">Actual: {{ camelText | snakecase }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>KebabCasePipe</h3>
|
||||
<div>Input: "helloWorld"</div>
|
||||
<div class="expected">Expected: hello-world</div>
|
||||
<div class="actual">Actual: {{ camelText | kebabcase }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>SubstrPipe</h3>
|
||||
<div>Input: "hello world" (0, 5)</div>
|
||||
<div class="expected">Expected: hello</div>
|
||||
<div class="actual">Actual: {{ text | substr:0:5 }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>DatePipe - Short</h3>
|
||||
<div>Input: Date</div>
|
||||
<div class="expected">Expected: MM/DD/YY, H:MM AM/PM</div>
|
||||
<div class="actual">Actual: {{ date | date:'short' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>DatePipe - Custom</h3>
|
||||
<div>Input: Date</div>
|
||||
<div class="expected">Expected: YYYY-MM-DD</div>
|
||||
<div class="actual">Actual: {{ date | date:'yyyy-MM-dd' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Pipe Chain</h3>
|
||||
<div>Input: "HELLO WORLD" | lowercase | camelcase</div>
|
||||
<div class="expected">Expected: helloWorld</div>
|
||||
<div class="actual">Actual: {{ upperText | lowercase | camelcase }}</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Pipe with || operator</h3>
|
||||
<div>Input: null || "default" | uppercase</div>
|
||||
<div class="expected">Expected: DEFAULT</div>
|
||||
<div class="actual">Actual: {{ nullValue || 'default' | uppercase }}</div>
|
||||
</div>
|
||||
`,
|
||||
imports: [
|
||||
UpperCasePipe,
|
||||
LowerCasePipe,
|
||||
JsonPipe,
|
||||
CamelCasePipe,
|
||||
PascalCasePipe,
|
||||
SnakeCasePipe,
|
||||
KebabCasePipe,
|
||||
SubstrPipe,
|
||||
DatePipe
|
||||
],
|
||||
})
|
||||
class TestPipesApp {
|
||||
text = signal('hello world');
|
||||
upperText = signal('HELLO WORLD');
|
||||
number = signal(123);
|
||||
str = signal('string');
|
||||
bool = signal(true);
|
||||
obj = signal({ name: 'Test', value: 123 });
|
||||
kebabText = signal('hello-world');
|
||||
camelText = signal('helloWorld');
|
||||
date = signal(new Date('2024-01-15T14:30:45'));
|
||||
nullValue = signal(null);
|
||||
}
|
||||
|
||||
bootstrapApplication(TestPipesApp, {
|
||||
providers: [],
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const results = document.getElementById('test-results');
|
||||
const sections = document.querySelectorAll('.test-section');
|
||||
|
||||
let html = '<h2>Test Results</h2>';
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
sections.forEach((section, index) => {
|
||||
const title = section.querySelector('h3').textContent;
|
||||
const actual = section.querySelector('.actual');
|
||||
const hasContent = actual && actual.textContent.trim().length > 'Actual: '.length;
|
||||
const hasUndefined = actual && actual.textContent.includes('undefined');
|
||||
|
||||
if (hasContent && !hasUndefined) {
|
||||
html += `<div class="pass">✓ ${title}</div>`;
|
||||
passed++;
|
||||
} else {
|
||||
html += `<div class="fail">✗ ${title} - ${hasUndefined ? 'undefined' : 'no content'}</div>`;
|
||||
failed++;
|
||||
}
|
||||
});
|
||||
|
||||
html += `<h3>Summary: ${passed} passed, ${failed} failed</h3>`;
|
||||
results.innerHTML = html;
|
||||
|
||||
console.log('Test Results:', { passed, failed });
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* Testy dla podstawowych pipes
|
||||
*/
|
||||
|
||||
import {
|
||||
UpperCasePipe,
|
||||
LowerCasePipe,
|
||||
JsonPipe,
|
||||
CamelCasePipe,
|
||||
PascalCasePipe,
|
||||
SnakeCasePipe,
|
||||
KebabCasePipe,
|
||||
SubstrPipe,
|
||||
DatePipe
|
||||
} from '../../core/pipes/index';
|
||||
|
||||
console.log('\n=== Test: Quarc Pipes ===\n');
|
||||
|
||||
const tests: { name: string; pass: boolean }[] = [];
|
||||
|
||||
// UpperCasePipe
|
||||
console.log('--- UpperCasePipe ---');
|
||||
const upperPipe = new UpperCasePipe();
|
||||
tests.push({ name: 'uppercase: hello → HELLO', pass: upperPipe.transform('hello') === 'HELLO' });
|
||||
tests.push({ name: 'uppercase: null → ""', pass: upperPipe.transform(null) === '' });
|
||||
tests.push({ name: 'uppercase: undefined → ""', pass: upperPipe.transform(undefined) === '' });
|
||||
|
||||
// LowerCasePipe
|
||||
console.log('--- LowerCasePipe ---');
|
||||
const lowerPipe = new LowerCasePipe();
|
||||
tests.push({ name: 'lowercase: HELLO → hello', pass: lowerPipe.transform('HELLO') === 'hello' });
|
||||
tests.push({ name: 'lowercase: null → ""', pass: lowerPipe.transform(null) === '' });
|
||||
|
||||
// JsonPipe
|
||||
console.log('--- JsonPipe ---');
|
||||
const jsonPipe = new JsonPipe();
|
||||
const obj = { name: 'Test', value: 123 };
|
||||
const jsonResult = jsonPipe.transform(obj);
|
||||
tests.push({ name: 'json: object serialized', pass: jsonResult.includes('"name"') && jsonResult.includes('"Test"') });
|
||||
tests.push({ name: 'json: array serialized', pass: jsonPipe.transform([1, 2, 3]).includes('[') });
|
||||
|
||||
// CamelCasePipe
|
||||
console.log('--- CamelCasePipe ---');
|
||||
const camelPipe = new CamelCasePipe();
|
||||
tests.push({ name: 'camelcase: hello-world → helloWorld', pass: camelPipe.transform('hello-world') === 'helloWorld' });
|
||||
tests.push({ name: 'camelcase: hello_world → helloWorld', pass: camelPipe.transform('hello_world') === 'helloWorld' });
|
||||
tests.push({ name: 'camelcase: hello world → helloWorld', pass: camelPipe.transform('hello world') === 'helloWorld' });
|
||||
tests.push({ name: 'camelcase: HelloWorld → helloWorld', pass: camelPipe.transform('HelloWorld') === 'helloWorld' });
|
||||
|
||||
// PascalCasePipe
|
||||
console.log('--- PascalCasePipe ---');
|
||||
const pascalPipe = new PascalCasePipe();
|
||||
tests.push({ name: 'pascalcase: hello-world → HelloWorld', pass: pascalPipe.transform('hello-world') === 'HelloWorld' });
|
||||
tests.push({ name: 'pascalcase: hello_world → HelloWorld', pass: pascalPipe.transform('hello_world') === 'HelloWorld' });
|
||||
tests.push({ name: 'pascalcase: hello world → HelloWorld', pass: pascalPipe.transform('hello world') === 'HelloWorld' });
|
||||
|
||||
// SnakeCasePipe
|
||||
console.log('--- SnakeCasePipe ---');
|
||||
const snakePipe = new SnakeCasePipe();
|
||||
tests.push({ name: 'snakecase: helloWorld → hello_world', pass: snakePipe.transform('helloWorld') === 'hello_world' });
|
||||
tests.push({ name: 'snakecase: HelloWorld → hello_world', pass: snakePipe.transform('HelloWorld') === 'hello_world' });
|
||||
tests.push({ name: 'snakecase: hello-world → hello_world', pass: snakePipe.transform('hello-world') === 'hello_world' });
|
||||
tests.push({ name: 'snakecase: hello world → hello_world', pass: snakePipe.transform('hello world') === 'hello_world' });
|
||||
|
||||
// KebabCasePipe
|
||||
console.log('--- KebabCasePipe ---');
|
||||
const kebabPipe = new KebabCasePipe();
|
||||
tests.push({ name: 'kebabcase: helloWorld → hello-world', pass: kebabPipe.transform('helloWorld') === 'hello-world' });
|
||||
tests.push({ name: 'kebabcase: HelloWorld → hello-world', pass: kebabPipe.transform('HelloWorld') === 'hello-world' });
|
||||
tests.push({ name: 'kebabcase: hello_world → hello-world', pass: kebabPipe.transform('hello_world') === 'hello-world' });
|
||||
tests.push({ name: 'kebabcase: hello world → hello-world', pass: kebabPipe.transform('hello world') === 'hello-world' });
|
||||
|
||||
// SubstrPipe
|
||||
console.log('--- SubstrPipe ---');
|
||||
const substrPipe = new SubstrPipe();
|
||||
tests.push({ name: 'substr: "hello"(0, 3) → "hel"', pass: substrPipe.transform('hello', 0, 3) === 'hel' });
|
||||
tests.push({ name: 'substr: "hello"(2) → "llo"', pass: substrPipe.transform('hello', 2) === 'llo' });
|
||||
tests.push({ name: 'substr: null → ""', pass: substrPipe.transform(null, 0) === '' });
|
||||
|
||||
// DatePipe
|
||||
console.log('--- DatePipe ---');
|
||||
const datePipe = new DatePipe();
|
||||
const testDate = new Date('2024-01-15T14:30:45');
|
||||
|
||||
const shortResult = datePipe.transform(testDate, 'short');
|
||||
tests.push({ name: 'date: short format contains date', pass: shortResult.includes('01') || shortResult.includes('1') });
|
||||
|
||||
const mediumResult = datePipe.transform(testDate, 'medium');
|
||||
tests.push({ name: 'date: medium format contains month', pass: mediumResult.includes('Jan') });
|
||||
|
||||
const customResult = datePipe.transform(testDate, 'yyyy-MM-dd');
|
||||
tests.push({ name: 'date: custom format yyyy-MM-dd', pass: customResult === '2024-01-15' });
|
||||
|
||||
const customTimeResult = datePipe.transform(testDate, 'HH:mm:ss');
|
||||
tests.push({ name: 'date: custom format HH:mm:ss', pass: customTimeResult === '14:30:45' });
|
||||
|
||||
tests.push({ name: 'date: null → ""', pass: datePipe.transform(null) === '' });
|
||||
tests.push({ name: 'date: invalid date → original', pass: datePipe.transform('invalid').includes('invalid') });
|
||||
|
||||
// Podsumowanie
|
||||
console.log('\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❌ PIPES TEST FAILED');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('\n✅ PIPES TEST PASSED');
|
||||
process.exit(0);
|
||||
}
|
||||
Loading…
Reference in New Issue