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
|
## Rozwiązanie
|
||||||
|
|
||||||
|
### 1. Naprawa rozróżniania operatorów logicznych
|
||||||
|
|
||||||
Dodano metodę `splitByPipe()` która poprawnie rozróżnia:
|
Dodano metodę `splitByPipe()` która poprawnie rozróżnia:
|
||||||
- Pojedynczy `|` - separator pipe
|
- Pojedynczy `|` - separator pipe
|
||||||
- Podwójny `||` - operator logiczny OR
|
- Podwójny `||` - operator logiczny OR
|
||||||
- Podwójny `&&` - operator logiczny AND
|
- 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
|
### Zmieniony plik
|
||||||
|
|
||||||
**`/web/quarc/cli/processors/template/template-transformer.ts`**
|
**`/web/quarc/cli/processors/template/template-transformer.ts`**
|
||||||
|
|
@ -50,13 +56,13 @@ private transformPipeExpression(expression: string): string {
|
||||||
|
|
||||||
if (colonIndex === -1) {
|
if (colonIndex === -1) {
|
||||||
const pipeName = pipePart.trim();
|
const pipeName = pipePart.trim();
|
||||||
result = `this._pipes?.['${pipeName}']?.transform(${result})`;
|
result = `_pipes?.['${pipeName}']?.transform(${result})`;
|
||||||
} else {
|
} else {
|
||||||
const pipeName = pipePart.substring(0, colonIndex).trim();
|
const pipeName = pipePart.substring(0, colonIndex).trim();
|
||||||
const argsStr = pipePart.substring(colonIndex + 1).trim();
|
const argsStr = pipePart.substring(colonIndex + 1).trim();
|
||||||
const args = argsStr.split(':').map(arg => arg.trim());
|
const args = argsStr.split(':').map(arg => arg.trim());
|
||||||
const argsJoined = args.join(', ');
|
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 }}
|
{{ value | uppercase }}
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
<span [inner-text]="this._pipes?.['uppercase']?.transform(value)"></span>
|
<span [inner-text]="_pipes?.['uppercase']?.transform(value)"></span>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Kombinacja || i pipe
|
### Kombinacja || i pipe
|
||||||
|
|
@ -138,7 +144,7 @@ Utworzono testy w `/web/quarc/tests/unit/`:
|
||||||
{{ (value || 'default') | uppercase }}
|
{{ (value || 'default') | uppercase }}
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
<span [inner-text]="this._pipes?.['uppercase']?.transform((value || 'default'))"></span>
|
<span [inner-text]="_pipes?.['uppercase']?.transform((value || 'default'))"></span>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Łańcuch pipes
|
### Łańcuch pipes
|
||||||
|
|
@ -147,7 +153,7 @@ Utworzono testy w `/web/quarc/tests/unit/`:
|
||||||
{{ value | lowercase | slice:0:5 }}
|
{{ value | lowercase | slice:0:5 }}
|
||||||
|
|
||||||
// Output
|
// 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
|
## Weryfikacja
|
||||||
|
|
|
||||||
|
|
@ -103,13 +103,13 @@ export class TemplateTransformer {
|
||||||
|
|
||||||
if (colonIndex === -1) {
|
if (colonIndex === -1) {
|
||||||
const pipeName = pipePart.trim();
|
const pipeName = pipePart.trim();
|
||||||
result = `this._pipes?.['${pipeName}']?.transform(${result})`;
|
result = `_pipes?.['${pipeName}']?.transform(${result})`;
|
||||||
} else {
|
} else {
|
||||||
const pipeName = pipePart.substring(0, colonIndex).trim();
|
const pipeName = pipePart.substring(0, colonIndex).trim();
|
||||||
const argsStr = pipePart.substring(colonIndex + 1).trim();
|
const argsStr = pipePart.substring(colonIndex + 1).trim();
|
||||||
const args = argsStr.split(':').map(arg => arg.trim());
|
const args = argsStr.split(':').map(arg => arg.trim());
|
||||||
const argsJoined = args.join(', ');
|
const argsJoined = args.join(', ');
|
||||||
result = `this._pipes?.['${pipeName}']?.transform(${result}, ${argsJoined})`;
|
result = `_pipes?.['${pipeName}']?.transform(${result}, ${argsJoined})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,6 @@ export { inject, setCurrentInjector } from "./angular/inject";
|
||||||
export type { ApplicationConfig, EnvironmentProviders, PluginConfig, PluginRoutingMode, Provider } from "./angular/app-config";
|
export type { ApplicationConfig, EnvironmentProviders, PluginConfig, PluginRoutingMode, Provider } from "./angular/app-config";
|
||||||
export { ComponentUtils } from "./utils/component-utils";
|
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);
|
console.log('Input:', test1);
|
||||||
const result1 = transformer.transformInterpolation(test1);
|
const result1 = transformer.transformInterpolation(test1);
|
||||||
console.log('Output:', result1);
|
console.log('Output:', result1);
|
||||||
const pass1 = !result1.includes('this._pipes') && result1.includes('||');
|
const pass1 = !result1.includes('_pipes?.') && result1.includes('||');
|
||||||
console.log('Pass:', pass1);
|
console.log('Pass:', pass1);
|
||||||
|
|
||||||
// Test 2: Operator && nie powinien być traktowany jako pipe
|
// Test 2: Operator && nie powinien być traktowany jako pipe
|
||||||
|
|
@ -23,7 +23,7 @@ console.log('\nTest 2: Operator &&');
|
||||||
console.log('Input:', test2);
|
console.log('Input:', test2);
|
||||||
const result2 = transformer.transformInterpolation(test2);
|
const result2 = transformer.transformInterpolation(test2);
|
||||||
console.log('Output:', result2);
|
console.log('Output:', result2);
|
||||||
const pass2 = !result2.includes('this._pipes') && result2.includes('&&');
|
const pass2 = !result2.includes('_pipes?.') && result2.includes('&&');
|
||||||
console.log('Pass:', pass2);
|
console.log('Pass:', pass2);
|
||||||
|
|
||||||
// Test 3: Prawdziwy pipe powinien być transformowany
|
// Test 3: Prawdziwy pipe powinien być transformowany
|
||||||
|
|
@ -32,7 +32,7 @@ console.log('\nTest 3: Prawdziwy pipe');
|
||||||
console.log('Input:', test3);
|
console.log('Input:', test3);
|
||||||
const result3 = transformer.transformInterpolation(test3);
|
const result3 = transformer.transformInterpolation(test3);
|
||||||
console.log('Output:', result3);
|
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);
|
console.log('Pass:', pass3);
|
||||||
|
|
||||||
// Test 4: Pipe z argumentami
|
// Test 4: Pipe z argumentami
|
||||||
|
|
@ -41,7 +41,7 @@ console.log('\nTest 4: Pipe z argumentami');
|
||||||
console.log('Input:', test4);
|
console.log('Input:', test4);
|
||||||
const result4 = transformer.transformInterpolation(test4);
|
const result4 = transformer.transformInterpolation(test4);
|
||||||
console.log('Output:', result4);
|
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);
|
console.log('Pass:', pass4);
|
||||||
|
|
||||||
// Test 5: Kombinacja || i pipe
|
// Test 5: Kombinacja || i pipe
|
||||||
|
|
@ -50,7 +50,7 @@ console.log('\nTest 5: Kombinacja || i pipe');
|
||||||
console.log('Input:', test5);
|
console.log('Input:', test5);
|
||||||
const result5 = transformer.transformInterpolation(test5);
|
const result5 = transformer.transformInterpolation(test5);
|
||||||
console.log('Output:', result5);
|
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);
|
console.log('Pass:', pass5);
|
||||||
|
|
||||||
// Test 6: Wielokrotne ||
|
// Test 6: Wielokrotne ||
|
||||||
|
|
@ -59,7 +59,7 @@ console.log('\nTest 6: Wielokrotne ||');
|
||||||
console.log('Input:', test6);
|
console.log('Input:', test6);
|
||||||
const result6 = transformer.transformInterpolation(test6);
|
const result6 = transformer.transformInterpolation(test6);
|
||||||
console.log('Output:', result6);
|
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);
|
console.log('Pass:', pass6);
|
||||||
|
|
||||||
// Test 7: Łańcuch pipes
|
// 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