- 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>
5.0 KiB
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 withLocalDataProvider(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. -
@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@uicontent 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:
// 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:
// ❌ 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. 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:
- Rewrite source-grep assertions → DOM queries via the helper. The
world.ts:hasText/hasField/hasElementmethods already prefer the rendered DOM and fall back to source — so unmigrated steps still work during the transition. - Delete tests on implementation details (
/showDuplicateWarning/,/importableEvents/, regex on JSX). They protect nothing the user sees. - Move behavioral assertions to
@e2ewhen not already covered ("clicking Suivant advances the wizard" — exercise it via Playwright if it's not redundant with existing journeys). - Drop redundant
@e2econtent checks that duplicate@ui(e.g. "screen contains 'Découvrir'" — let@uiown 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 — Cucumber config, file layout, scripts
- Data Layer — NextGraph shapes, seed data, contexts
- Data-Layer Testing — broker harness, wallet setup, Playwright
- Architecture — module structure