feat(fp): WP-19 — Playwright e2e smoke against the real FE+backend
Adds a happy-path spec (login → dashboard → registratie wizard, including a real identity-document upload → real submit) and a degraded-path spec (?scenario=error → <app-async> error slot → retry), both driving the real app against the real .NET backend, plus a CI job that boots both. Writing the retry spec surfaced a real bug: AsyncComponent's retry() only reloads a [resource]-fed instance, so every real page (all [data]-fed via a store's RemoteData) had a silently no-op retry button. Added a retryClicked output and wired it on the dashboard's two async blocks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -72,4 +72,12 @@ export class BigProfileStore {
|
||||
rollbackHerregistratie() {
|
||||
this.pending.set(false); // submission failed — undo the optimistic flag
|
||||
}
|
||||
|
||||
// Retry hooks for [data]-fed <app-async> instances (they don't own the resource).
|
||||
reloadProfile() {
|
||||
this.viewRes.reload();
|
||||
}
|
||||
reloadAantekeningen() {
|
||||
this.aantekeningenRes.reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
|
||||
>
|
||||
}
|
||||
|
||||
<app-async [data]="store.profile()">
|
||||
<app-async [data]="store.profile()" (retryClicked)="store.reloadProfile()">
|
||||
<ng-template appAsyncLoaded>
|
||||
@if (profile(); as p) {
|
||||
@let tasks = tasksFor(p.registration);
|
||||
@@ -153,7 +153,7 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
|
||||
>Specialismen en aantekeningen</app-heading
|
||||
>
|
||||
<div class="app-section">
|
||||
<app-async [data]="store.aantekeningen()">
|
||||
<app-async [data]="store.aantekeningen()" (retryClicked)="store.reloadAantekeningen()">
|
||||
<ng-template appAsyncLoaded>
|
||||
@if (aantekeningen(); as r) {
|
||||
<app-registration-table [rows]="r" />
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
Directive,
|
||||
TemplateRef,
|
||||
computed,
|
||||
contentChild,
|
||||
input,
|
||||
output,
|
||||
} from '@angular/core';
|
||||
import { NgTemplateOutlet } from '@angular/common';
|
||||
import type { Resource } from '@angular/core';
|
||||
import { SpinnerComponent } from '@shared/ui/spinner/spinner.component';
|
||||
@@ -130,11 +138,16 @@ export class AsyncComponent<T> {
|
||||
}),
|
||||
);
|
||||
|
||||
// [resource]-fed callers get reload() for free. [data]-fed callers (a store's
|
||||
// combined RemoteData — the component doesn't own that resource) must reload
|
||||
// it themselves; retryClicked is how they find out a retry was requested.
|
||||
retryClicked = output<void>();
|
||||
retry = () => {
|
||||
const r = this.resource();
|
||||
if (r && 'reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {
|
||||
(r as { reload: () => void }).reload();
|
||||
}
|
||||
this.retryClicked.emit();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user