Modern UI port, render-based @ui tests, dev seed, layer contracts

- Port modern clean theme (DM Sans, orange accent, app-* CSS classes)
  and screen redesigns from festipod-mockups; replace sketchy Ubuntu
  theme. New shared components: BottomNav, EventCover, EventMeetingPoints,
  Toast, AvatarStack, Tag, RelevanceIcon.

- Restructure from prototyping shell to real mobile web app:
  path-based routing (History API), Gallery/DemoMode/PhoneFrame removed,
  Storybook setup for screen/component browsing.

- ConnectScreen ported from mockup (QR-based user connection); routed
  at /profile/connect, wired from FriendsListScreen.

- Dev-only auto-seed of NG wallet when empty
  (gated on NODE_ENV !== 'production'); bootstrapWallet already
  self-checks for non-empty ngSet so safe even in race conditions.

- Render-based @ui test infrastructure: happy-dom + LocalDataProvider +
  RouterProvider via src/shared/test-harness/renderHelper.tsx, exposed
  on the world as renderedDoc. world.hasText/hasField/hasElement prefer
  the rendered DOM and fall back to source for backward compatibility.

- Migrate 25 brittle @ui assertions from regex-on-source to DOM
  queries; delete implementation-detail tests (showDuplicateWarning,
  importableEvents, importedFrom — anti-patterns per the new contract).
  Update feature files where the UI changed: "Mes amis" → "Mon réseau",
  "Mes événements à venir" → "À venir" on home, Thématique removed
  from create-event wizard, etc.

- Path-based @e2e steps (pushState + popstate dispatch) replacing the
  legacy "#/demo/…" hash routing tied to the deleted Gallery.

- Add .project/knowledge/test-layer-contracts.md defining the role of
  each test layer (@ui = display with seed data + DOM, @data = mutations
  through NG broker, @e2e = critical user journeys) with anti-patterns
  and migration consequences.

Test status: 75 passed / 71 skipped (explicit "non implémenté")
/ 2 failed (pre-existing @wip on ngSet.delete() NG ORM limitation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-05-18 09:49:50 +02:00
parent 7099c817db
commit 5a29938130
91 changed files with 5474 additions and 6362 deletions
@@ -0,0 +1,90 @@
# Test Layer Contracts
Each BDD test layer (`@ui`, `@data`, `@e2e`) answers a distinct question. Mixing concerns produces brittle tests that fail on refactors without catching real regressions.
## Overview
```
/\ @e2e ~10 scénarios, parcours utilisateur critiques
/ \
/----\
/ @data\ ~10 scénarios, mutations & persistance NG
/--------\
/ @ui \ ~60 scénarios, 1-5 par état d'écran × 15 écrans
/____________\
```
The pyramid reflects cost: `@ui` runs in-process (instant), `@data` boots a broker (~50s for the suite), `@e2e` boots broker + real app + navigates a real browser (~2min). Move every assertion to the lowest layer that can answer the question — UI rendering claims belong in `@ui`, not `@e2e`.
## Key Concepts
- **`@ui` — display layer.** Renders a screen with `LocalDataProvider` (seed data) + happy-dom and asserts on the resulting DOM. Verifies that *given known data, the screen shows the expected text and elements*. Does **not** test navigation outcomes, mutations, or data persistence.
- **`@data` — data layer.** Drives ORM mutations through the real NextGraph broker via a headless test harness. No app UI involved. Verifies that *operations on shapes are correctly persisted and observable in the wallet*. See [data-layer-testing](./data-layer-testing.md).
- **`@e2e` — integration layer.** Boots the real app inside the broker iframe with a Playwright-controlled Chromium. Verifies that *layers collaborate to deliver a user journey* (e.g. create → list → modify → reload → still there). Sparse: 1 scenario per critical path; never duplicate `@ui` content checks here.
## Implementation
### `@ui` — rendering helper
`src/shared/test-harness/renderHelper.tsx` installs happy-dom globals and renders any screen wrapped in `LocalDataProvider` + `RouterProvider`. Called from `world.ts:renderCurrentScreen()` on every `navigateTo(...)`. Seed data (`src/shared/data/seedData.ts`) provides predictable fixtures — `Marie Dupont`/`@mariedupont` is `currentUser`, `Jean Durand`/`@jeandurand` exists in `users`, 5 seed events, etc.
**Good `@ui` assertion patterns:**
```ts
// Text visible to the user
expect(this.getDomText()).to.include('Marie Dupont');
// Element presence by class/role
expect(this.renderedDoc!.querySelector('.app-avatar')).to.not.be.null;
// Conditional rendering (filled state vs empty state)
const cards = this.renderedDoc!.querySelectorAll('.app-card');
expect(cards.length).to.be.greaterThan(0);
// Required form fields rendered with their label + asterisk
const labels = Array.from(this.renderedDoc!.querySelectorAll('p'))
.map(p => p.textContent ?? '');
expect(labels.some(t => t.includes("Nom de l'événement *"))).to.be.true;
```
**Anti-patterns to remove:**
```ts
// ❌ Regex on source: couples test to code structure, fails on refactor
expect(/<Title[^>]*>Marie Dupont<\/Title>/.test(source)).to.be.true;
// ❌ Testing implementation details
expect(/showDuplicateWarning/.test(source)).to.be.true;
expect(/importableEvents/.test(source)).to.be.true;
// ❌ Testing JSX structure rather than rendered output
expect(/<Avatar[^>]*initials="MD"[^>]*size="lg"/.test(source)).to.be.true;
```
### `@data` — broker-only
Already isolated correctly. See [data-layer-testing](./data-layer-testing.md). Don't touch the DOM here; use the test harness bridge (`window.__testData`).
### `@e2e` — full stack
Path-based routing: navigate via `window.history.pushState` + `popstate` dispatch (`src/modules/auth/steps/e2e/connexion.steps.ts`). Assert on actual DOM text after `appFrame.waitForFunction`. **Do not** re-verify here what `@ui` already covers — `@e2e` should fail when *collaboration* between layers breaks, not when an icon changes.
## Migration Consequences
The current `@ui` suite predates this contract. The migration plan:
1. **Rewrite source-grep assertions** → DOM queries via the helper. The `world.ts:hasText/hasField/hasElement` methods already prefer the rendered DOM and fall back to source — so unmigrated steps still work during the transition.
2. **Delete tests on implementation details** (`/showDuplicateWarning/`, `/importableEvents/`, regex on JSX). They protect nothing the user sees.
3. **Move behavioral assertions to `@e2e`** when not already covered ("clicking Suivant advances the wizard" — exercise it via Playwright if it's not redundant with existing journeys).
4. **Drop redundant `@e2e` content checks** that duplicate `@ui` (e.g. "screen contains 'Découvrir'" — let `@ui` own that).
`world.ts:screenFileMap`, `screenFieldDetectors`, `screenExpectedContent`, `screenRequiredFields` are vestiges of the source-analysis era. Once the migration is complete, they can be removed in favor of seed-data assertions on the rendered DOM.
## See Also
- [BDD Testing setup](./bdd-testing.md) — Cucumber config, file layout, scripts
- [Data Layer](./data-layer.md) — NextGraph shapes, seed data, contexts
- [Data-Layer Testing](./data-layer-testing.md) — broker harness, wallet setup, Playwright
- [Architecture](./architecture.md) — module structure