Rewrite step definitions with inline detection logic
- Replace abstraction functions with inline regex patterns in step definitions - Add clear test outcomes: Pass/Fail for testable features, Pending with specific prefixes (NOT IMPLEMENTED, CANNOT TEST, WRONG STEP, NOT ON THIS SCREEN) for non-testable features - Fix GherkinHighlighter to use step.text instead of step.originalLine for step definition matching - Update documentation with Test Outcomes section - Extend test:cucumber script to run all parsing steps Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+157
-21
@@ -17,7 +17,7 @@ Step Definition Matching
|
||||
↓
|
||||
World Instance (FestipodWorld)
|
||||
↓
|
||||
Screen Source Analysis (regex field detectors)
|
||||
Inline Detection Logic (screen-specific regex patterns)
|
||||
↓
|
||||
Chai Assertions
|
||||
↓
|
||||
@@ -29,8 +29,8 @@ JSON + HTML Reports
|
||||
```
|
||||
features/
|
||||
├── support/
|
||||
│ ├── world.ts # Custom World class with state management
|
||||
│ └── hooks.ts # Before/After lifecycle hooks
|
||||
│ ├── world.ts # Custom World class with state management
|
||||
│ └── hooks.ts # Before/After lifecycle hooks
|
||||
├── step_definitions/
|
||||
│ ├── navigation.steps.ts # Screen navigation steps
|
||||
│ ├── form.steps.ts # Form validation steps
|
||||
@@ -131,7 +131,70 @@ Field detection is screen-specific, defined in `screenFieldDetectors` map. Each
|
||||
| Pseudo | `@username` pattern |
|
||||
| Photo / Photo de profil | `<Avatar` component |
|
||||
|
||||
This approach makes tests resilient to minor UI changes while still validating the semantic structure of screens.
|
||||
These patterns match what currently exists in each screen's source code. When UI changes, patterns must be updated accordingly.
|
||||
|
||||
## Step Definition Design Principles
|
||||
|
||||
Step definitions follow strict principles to ensure they are **readable**, **specific**, and **verifiable**:
|
||||
|
||||
### 1. Inline Detection Logic (No Abstraction)
|
||||
|
||||
All detection logic must be written directly in step definitions, not in separate utility functions. This is critical because:
|
||||
- The step definition code is displayed in the app's "Définitions" view
|
||||
- Users must see the exact detection logic when reviewing specs
|
||||
- Abstraction hides the "how" and prevents quick verification
|
||||
|
||||
**Correct approach:**
|
||||
```typescript
|
||||
Then('je peux annuler et revenir à l\'écran précédent', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('create-event');
|
||||
const source = this.getRenderedText();
|
||||
// Detect ✕ close button with onClick handler that calls navigate()
|
||||
const found = /onClick\s*=\s*\{\s*\(\)\s*=>\s*navigate\s*\(['"]home['"]\)\s*\}[^>]*>✕</.test(source);
|
||||
expect(found, 'Create event screen should have ✕ button with navigate("home")').to.be.true;
|
||||
});
|
||||
```
|
||||
|
||||
**Avoid (abstraction hides logic):**
|
||||
```typescript
|
||||
// DON'T DO THIS - the function hides the detection logic from the UI
|
||||
const result = hasBackNavigation(source);
|
||||
expect(result.found).to.be.true;
|
||||
```
|
||||
|
||||
### 2. Specific Patterns (No Future Anticipation)
|
||||
|
||||
Detection patterns must match **exactly what exists in the codebase today**. Do not use OR patterns or generic matching to anticipate future variations that don't exist yet.
|
||||
|
||||
**Correct approach:**
|
||||
```typescript
|
||||
// CreateEventScreen.tsx has: onClick={() => navigate('home')}...>✕<
|
||||
const found = /onClick\s*=\s*\{\s*\(\)\s*=>\s*navigate\s*\(['"]home['"]\)\s*\}[^>]*>✕</.test(source);
|
||||
```
|
||||
|
||||
**Avoid (anticipates variations that don't exist):**
|
||||
```typescript
|
||||
// DON'T DO THIS - the app doesn't have "Retour", "←", or "Annuler" buttons
|
||||
const found = /✕|←|Retour|Annuler/.test(source);
|
||||
```
|
||||
|
||||
### 3. Code Duplication is Acceptable
|
||||
|
||||
If multiple steps need similar detection logic, duplicate the code. This maintains readability and allows each step to evolve independently based on actual screen content.
|
||||
|
||||
### 4. Screen-Specific Detection
|
||||
|
||||
Each step should know exactly which screen it's testing and what specific patterns to look for in that screen's source code.
|
||||
|
||||
### When Tests Fail
|
||||
|
||||
A failing test means a specific UI element was removed or changed:
|
||||
|
||||
```
|
||||
AssertionError: Create event screen should have ✕ button with navigate("home")
|
||||
```
|
||||
|
||||
This tells the developer exactly what's missing and where to look. The regex in the step definition shows exactly what pattern was expected.
|
||||
|
||||
## Step Definitions
|
||||
|
||||
@@ -178,16 +241,87 @@ Alors une erreur de validation est affichée pour "Date"
|
||||
Alors je peux voir la liste des participants
|
||||
Alors l'écran affiche les informations de l'événement
|
||||
|
||||
# Feature detection (returns 'pending' if not implemented)
|
||||
# Feature detection (may return 'pending' - see Test Outcomes section below)
|
||||
Alors je peux ajouter un commentaire
|
||||
Alors je peux ajouter une note
|
||||
Alors je peux modifier un commentaire
|
||||
Alors je peux supprimer un commentaire
|
||||
Alors je peux m'inscrire à l'événement
|
||||
Alors je peux voir le QR code
|
||||
Alors je peux filtrer les événements par période
|
||||
```
|
||||
|
||||
## Test Outcomes
|
||||
|
||||
Every step definition must produce one of two outcomes:
|
||||
|
||||
### 1. Pass/Fail (Testable)
|
||||
|
||||
When we **can verify** the feature through static source analysis:
|
||||
- The step runs assertions (`expect(...).to.be.true`)
|
||||
- If the assertion passes → test passes
|
||||
- If the assertion fails → test fails with descriptive error message
|
||||
|
||||
```typescript
|
||||
Then('je peux voir la liste des participants', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('event-detail');
|
||||
const source = this.getRenderedText();
|
||||
// EventDetailScreen.tsx has: <Avatar components and "Participants (12)" text
|
||||
const hasAvatars = /<Avatar/.test(source);
|
||||
expect(hasAvatars, 'Event detail should have Avatar components for participants').to.be.true;
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Pending (Not Testable)
|
||||
|
||||
When we **cannot verify** the feature, the step must return `'pending'` with an explanatory message. There are four reasons a test may be pending:
|
||||
|
||||
| Prefix | Reason | Example |
|
||||
|--------|--------|---------|
|
||||
| `NOT IMPLEMENTED` | Feature doesn't exist in the UI | Comment functionality not in EventDetailScreen.tsx |
|
||||
| `CANNOT TEST` | Requires browser automation, backend, or database | Form submission requires browser automation |
|
||||
| `WRONG STEP` | Step is being used on inappropriate screen type | "le formulaire contient..." on a display screen |
|
||||
| `NOT ON THIS SCREEN` | Feature exists but on a different screen | QR code is on share-profile, not profile |
|
||||
|
||||
```typescript
|
||||
// NOT IMPLEMENTED - feature doesn't exist
|
||||
Then('je peux ajouter un commentaire', async function (this: FestipodWorld) {
|
||||
this.attach('NOT IMPLEMENTED: Comment functionality not in EventDetailScreen.tsx', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
// CANNOT TEST - requires browser automation
|
||||
When('je remplis le champ {string} avec {string}', async function (this: FestipodWorld, fieldName: string, value: string) {
|
||||
this.attach(`CANNOT TEST: Filling field "${fieldName}" with "${value}" requires browser automation`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
// WRONG STEP - step used on wrong screen type
|
||||
Then('le formulaire contient le champ obligatoire {string}', async function (this: FestipodWorld, fieldName: string) {
|
||||
if (this.currentScreenId !== 'create-event') {
|
||||
this.attach(`WRONG STEP: "le formulaire contient le champ obligatoire" is for forms. Screen "${this.currentScreenId}" is not a form.`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
// ... actual test logic ...
|
||||
});
|
||||
|
||||
// NOT ON THIS SCREEN - feature exists elsewhere
|
||||
Then('je peux voir le QR code', async function (this: FestipodWorld) {
|
||||
const source = this.getRenderedText();
|
||||
if (this.currentScreenId === 'share-profile') {
|
||||
expect(/QR Code/.test(source), 'Share profile should have "QR Code" text').to.be.true;
|
||||
} else {
|
||||
this.attach(`NOT ON THIS SCREEN: QR code is on share-profile, not "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### No Silent Tests
|
||||
|
||||
**Critical rule**: A test must never do nothing. Every step definition must either:
|
||||
1. Run assertions that can pass or fail, OR
|
||||
2. Return `'pending'` with an explanatory message
|
||||
|
||||
This ensures the test suite provides clear feedback about what is tested, what is not testable, and why.
|
||||
|
||||
## Hooks
|
||||
|
||||
Lifecycle hooks in `features/support/hooks.ts`:
|
||||
@@ -230,12 +364,15 @@ When a scenario fails, the `After` hook attaches:
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests end-to-end (runs tests + generates internal report)
|
||||
# Run all tests end-to-end (runs tests + generates all data files)
|
||||
bun run test:cucumber
|
||||
# This runs: cucumber:run → cucumber:report → features:parse → steps:extract
|
||||
|
||||
# Sub-commands for individual steps:
|
||||
bun run cucumber:run # Only run cucumber tests (generates HTML/JSON reports)
|
||||
bun run cucumber:report # Only parse results to generate internal report
|
||||
bun run cucumber:report # Parse results to generate testResults.ts
|
||||
bun run features:parse # Parse .feature files to generate features.ts
|
||||
bun run steps:extract # Extract step definitions to generate stepDefinitions.ts
|
||||
|
||||
# Run by category tag
|
||||
bun run cucumber:run --tags "@USER"
|
||||
@@ -251,17 +388,12 @@ bun run cucumber:run --tags "not @pending"
|
||||
|
||||
## Parsing Results
|
||||
|
||||
After running tests, parse results for the UI:
|
||||
All parsing is included in `bun run test:cucumber`. For manual regeneration:
|
||||
|
||||
```bash
|
||||
# Generate testResults.ts from cucumber-report.json (included in test:cucumber)
|
||||
bun run cucumber:report
|
||||
|
||||
# Regenerate step definitions data
|
||||
bun run steps:extract
|
||||
|
||||
# Parse feature files for UI display
|
||||
bun run features:parse
|
||||
bun run cucumber:report # testResults.ts from cucumber-report.json
|
||||
bun run steps:extract # stepDefinitions.ts from step definition files
|
||||
bun run features:parse # features.ts from .feature files
|
||||
```
|
||||
|
||||
## Example Feature File
|
||||
@@ -310,9 +442,13 @@ All Gherkin keywords and step definitions use French:
|
||||
- `Quand` instead of `When`
|
||||
- `Alors` instead of `Then`
|
||||
|
||||
### Semantic Field Detection
|
||||
### Specific Detection (Not Generic)
|
||||
|
||||
Rather than checking for specific CSS selectors or test IDs, the integration uses semantic patterns to detect features. For example, detecting a "Date" field by looking for the 📅 emoji pattern makes tests resilient to UI changes.
|
||||
Tests use patterns that match exactly what exists in the codebase today:
|
||||
- No OR patterns to anticipate future variations
|
||||
- No abstraction functions that hide detection logic
|
||||
- Tests break when UI changes - this is intentional to catch regressions
|
||||
- When a test fails, the developer must update both the UI and the test pattern
|
||||
|
||||
## UI Integration
|
||||
|
||||
|
||||
@@ -37,6 +37,6 @@ Fonctionnalité: US-13 Créer/Modifier/Supprimer un événement
|
||||
Étant donné je suis sur la page "créer un événement"
|
||||
Alors l'écran contient une section "Créer l'événement"
|
||||
|
||||
Scénario: Vérifier la présence du bouton d'annulation
|
||||
Scénario: Pouvoir annuler la création d'événement
|
||||
Étant donné je suis sur la page "créer un événement"
|
||||
Alors l'écran contient une section "Annuler"
|
||||
Alors je peux annuler et revenir à l'écran précédent
|
||||
|
||||
@@ -14,63 +14,91 @@ Given('le formulaire de création est vide', async function (this: FestipodWorld
|
||||
});
|
||||
|
||||
When('je remplis le champ {string} avec {string}', async function (this: FestipodWorld, fieldName: string, value: string) {
|
||||
const existing = this.formFields.get(fieldName);
|
||||
this.formFields.set(fieldName, {
|
||||
required: existing?.required ?? false,
|
||||
value
|
||||
});
|
||||
// Cannot fill form fields without browser automation
|
||||
this.attach(`CANNOT TEST: Filling field "${fieldName}" with "${value}" requires browser automation`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
When('je laisse le champ {string} vide', async function (this: FestipodWorld, fieldName: string) {
|
||||
const existing = this.formFields.get(fieldName);
|
||||
if (existing) {
|
||||
this.formFields.set(fieldName, { ...existing, value: '' });
|
||||
}
|
||||
// Cannot manipulate form fields without browser automation
|
||||
this.attach(`CANNOT TEST: Leaving field "${fieldName}" empty requires browser automation`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
When('je soumets le formulaire', async function (this: FestipodWorld) {
|
||||
this.attach('Form submitted', 'text/plain');
|
||||
// Cannot submit forms without browser automation
|
||||
this.attach('CANNOT TEST: Form submission requires browser automation', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('le formulaire contient le champ obligatoire {string}', async function (this: FestipodWorld, fieldName: string) {
|
||||
const field = this.formFields.get(fieldName);
|
||||
expect(field, `Field "${fieldName}" should exist`).to.not.be.undefined;
|
||||
expect(field?.required, `Field "${fieldName}" should be required`).to.equal(true);
|
||||
// This step is for form screens only (create-event)
|
||||
// For display screens, use different steps
|
||||
if (this.currentScreenId !== 'create-event') {
|
||||
this.attach(`WRONG STEP: "le formulaire contient le champ obligatoire" is for forms. Screen "${this.currentScreenId}" is not a form.`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
const source = this.getRenderedText();
|
||||
// CreateEventScreen.tsx: Required fields have " *" after label: >Label *<
|
||||
const escapedName = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`>${escapedName}\\s*\\*<`);
|
||||
expect(pattern.test(source), `Field "${fieldName}" should be marked as required (with *) in create-event screen`).to.be.true;
|
||||
});
|
||||
|
||||
Then('le formulaire contient les champs obligatoires suivants:', async function (this: FestipodWorld, dataTable) {
|
||||
// This step is for form screens only (create-event)
|
||||
// For display screens, use different steps
|
||||
if (this.currentScreenId !== 'create-event') {
|
||||
this.attach(`WRONG STEP: "le formulaire contient les champs obligatoires" is for forms. Screen "${this.currentScreenId}" is not a form.`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
const source = this.getRenderedText();
|
||||
const expectedFields = dataTable.raw().flat();
|
||||
expectedFields.forEach((fieldName: string) => {
|
||||
const field = this.formFields.get(fieldName);
|
||||
expect(field, `Field "${fieldName}" should exist`).to.not.be.undefined;
|
||||
expect(field?.required, `Field "${fieldName}" should be required`).to.equal(true);
|
||||
// CreateEventScreen.tsx: Required fields have " *" after label: >Label *<
|
||||
const escapedName = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`>${escapedName}\\s*\\*<`);
|
||||
expect(pattern.test(source), `Field "${fieldName}" should be marked as required (with *) in create-event screen`).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
Then('le champ {string} est facultatif', async function (this: FestipodWorld, fieldName: string) {
|
||||
const field = this.formFields.get(fieldName);
|
||||
if (field) {
|
||||
expect(field.required).to.equal(false);
|
||||
}
|
||||
const source = this.getRenderedText();
|
||||
// Optional fields have label without " *": >Label< followed by Input
|
||||
const escapedName = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
// Check field exists but NOT marked as required
|
||||
const existsPattern = new RegExp(`>${escapedName}<`);
|
||||
const requiredPattern = new RegExp(`>${escapedName}\\s*\\*<`);
|
||||
expect(existsPattern.test(source), `Field "${fieldName}" should exist in screen`).to.be.true;
|
||||
expect(requiredPattern.test(source), `Field "${fieldName}" should NOT be marked as required`).to.be.false;
|
||||
});
|
||||
|
||||
Then('le champ {string} affiche {string}', async function (this: FestipodWorld, fieldName: string, expectedValue: string) {
|
||||
const field = this.formFields.get(fieldName);
|
||||
expect(field?.value).to.equal(expectedValue);
|
||||
// Cannot verify displayed field values without browser automation
|
||||
this.attach(`CANNOT TEST: Verifying field "${fieldName}" displays "${expectedValue}" requires browser automation`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('le champ {string} est présent', async function (this: FestipodWorld, fieldName: string) {
|
||||
const field = this.formFields.get(fieldName);
|
||||
expect(field, `Field "${fieldName}" should exist`).to.not.be.undefined;
|
||||
const source = this.getRenderedText();
|
||||
// Check that field label exists in screen source
|
||||
const escapedName = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`>${escapedName}[^<]*<`);
|
||||
const found = pattern.test(source);
|
||||
if (!found) {
|
||||
this.attach(`NOT FOUND: Field "${fieldName}" not present in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
Then('une erreur de validation est affichée pour {string}', async function (this: FestipodWorld, fieldName: string) {
|
||||
const field = this.formFields.get(fieldName);
|
||||
expect(field?.required).to.equal(true);
|
||||
expect(field?.value).to.equal('');
|
||||
this.attach(`Validation error for: ${fieldName}`, 'text/plain');
|
||||
// Cannot verify validation errors without browser automation
|
||||
this.attach(`CANNOT TEST: Validation error for "${fieldName}" requires browser automation`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('le formulaire affiche {int} champs', async function (this: FestipodWorld, count: number) {
|
||||
expect(this.formFields.size).to.equal(count);
|
||||
// Cannot count form fields without specific analysis
|
||||
this.attach(`CANNOT TEST: Counting ${count} form fields requires more specific screen analysis`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
@@ -51,15 +51,39 @@ When('je navigue vers {string}', async function (this: FestipodWorld, pageName:
|
||||
});
|
||||
|
||||
When('je clique sur {string}', async function (this: FestipodWorld, elementName: string) {
|
||||
this.attach(`Clicked on: ${elementName}`, 'text/plain');
|
||||
const source = this.getRenderedText();
|
||||
// Check that a clickable element with this text exists (onClick handler + text content)
|
||||
const escapedName = elementName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`onClick[^>]*>[^<]*${escapedName}`, 'i');
|
||||
const found = pattern.test(source);
|
||||
if (!found) {
|
||||
this.attach(`MISSING: Clickable element "${elementName}" not found in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
When('je sélectionne {string}', async function (this: FestipodWorld, elementName: string) {
|
||||
this.attach(`Selected: ${elementName}`, 'text/plain');
|
||||
const source = this.getRenderedText();
|
||||
// Check that a selectable element with this text exists
|
||||
const escapedName = elementName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`onClick[^>]*>[^<]*${escapedName}`, 'i');
|
||||
const found = pattern.test(source);
|
||||
if (!found) {
|
||||
this.attach(`MISSING: Selectable element "${elementName}" not found in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
When('je clique sur le bouton {string}', async function (this: FestipodWorld, buttonName: string) {
|
||||
this.attach(`Clicked button: ${buttonName}`, 'text/plain');
|
||||
const source = this.getRenderedText();
|
||||
// Check that a Button component with this label exists
|
||||
const escapedName = buttonName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`<Button[^>]*>[^<]*${escapedName}[^<]*</Button>`, 'i');
|
||||
const found = pattern.test(source);
|
||||
if (!found) {
|
||||
this.attach(`MISSING: Button "${buttonName}" not found in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
When('je clique sur un participant', async function (this: FestipodWorld) {
|
||||
@@ -86,15 +110,42 @@ Then('je reste sur la page {string}', async function (this: FestipodWorld, pageN
|
||||
});
|
||||
|
||||
Then('l\'écran contient une section {string}', async function (this: FestipodWorld, sectionName: string) {
|
||||
expect(this.currentScreenId).to.not.be.null;
|
||||
this.attach(`Verified section: ${sectionName}`, 'text/plain');
|
||||
const found = this.hasText(sectionName);
|
||||
if (!found) {
|
||||
this.attach(`MISSING SECTION: "${sectionName}" not found in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
Then('je peux annuler et revenir à l\'écran précédent', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('create-event');
|
||||
const source = this.getRenderedText();
|
||||
// Detect ✕ close button with onClick handler that calls navigate()
|
||||
const found = /onClick\s*=\s*\{\s*\(\)\s*=>\s*navigate\s*\(['"]home['"]\)\s*\}[^>]*>✕</.test(source);
|
||||
expect(found, 'Create event screen should have ✕ button with navigate("home")').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux naviguer vers {string}', async function (this: FestipodWorld, pageName: string) {
|
||||
const screenId = resolveScreenId(pageName);
|
||||
this.attach(`Navigation available to: ${screenId}`, 'text/plain');
|
||||
const source = this.getRenderedText();
|
||||
// Check that a navigation link to this screen exists: navigate('screenId') or onClick={() => navigate('screenId')}
|
||||
const pattern = new RegExp(`navigate\\s*\\(\\s*['"]${screenId}['"]\\s*\\)`);
|
||||
const found = pattern.test(source);
|
||||
if (!found) {
|
||||
this.attach(`MISSING: Navigation to "${screenId}" not found in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
Then('la navigation affiche {string} comme actif', async function (this: FestipodWorld, menuItem: string) {
|
||||
this.attach(`Active menu: ${menuItem}`, 'text/plain');
|
||||
const source = this.getRenderedText();
|
||||
// Check that NavBar has an item with this label and active: true
|
||||
// Pattern: { icon: '...', label: 'menuItem', active: true }
|
||||
const escapedItem = menuItem.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`label:\\s*['"]${escapedItem}['"][^}]*active:\\s*true`, 'i');
|
||||
const found = pattern.test(source);
|
||||
if (!found) {
|
||||
this.attach(`MISSING: Menu item "${menuItem}" is not active in NavBar of screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,79 +1,119 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber';
|
||||
import { Given, Then } from '@cucumber/cucumber';
|
||||
import { expect } from 'chai';
|
||||
import type { FestipodWorld } from '../support/world';
|
||||
import { screenExpectedContent } from '../support/world';
|
||||
|
||||
Then('je peux voir la liste des participants', async function (this: FestipodWorld) {
|
||||
const screensWithParticipants = ['event-detail', 'participants-list', 'invite'];
|
||||
expect(screensWithParticipants, `Screen ${this.currentScreenId} should show participants`).to.include(this.currentScreenId);
|
||||
|
||||
// Verify the text "Participant" appears in the rendered content
|
||||
const hasParticipants = this.hasText('Participant') || this.hasText('participant') || this.hasText('inscrits');
|
||||
expect(hasParticipants, 'Page should display participants list').to.be.true;
|
||||
expect(this.currentScreenId).to.equal('event-detail');
|
||||
const source = this.getRenderedText();
|
||||
// EventDetailScreen.tsx has: <Avatar components and "Participants (12)" text
|
||||
const hasAvatars = /<Avatar/.test(source);
|
||||
const hasParticipantsSection = /Participants\s*\(\d+\)/.test(source);
|
||||
expect(hasAvatars, 'Event detail should have Avatar components for participants').to.be.true;
|
||||
expect(hasParticipantsSection, 'Event detail should have "Participants (N)" section').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux voir les détails de l\'événement', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('event-detail');
|
||||
// Verify event detail content is rendered
|
||||
const hasEventInfo = this.hasText('Description') || this.hasText('Participant') || this.hasText('inscrits');
|
||||
expect(hasEventInfo, 'Event detail page should show event information').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
// EventDetailScreen.tsx has: <Title>, 📅, 🕓, 📍 emojis, and "À propos" section
|
||||
expect(/<Title[^>]*>[^<]+<\/Title>/.test(source), 'Event detail should have a Title').to.be.true;
|
||||
expect(/📅/.test(source), 'Event detail should have date emoji 📅').to.be.true;
|
||||
expect(/🕓/.test(source), 'Event detail should have time emoji 🕓').to.be.true;
|
||||
expect(/📍/.test(source), 'Event detail should have location emoji 📍').to.be.true;
|
||||
expect(/À propos/.test(source), 'Event detail should have "À propos" section').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux voir la section {string}', async function (this: FestipodWorld, sectionName: string) {
|
||||
const hasSection = this.hasText(sectionName);
|
||||
if (!hasSection) {
|
||||
const source = this.getRenderedText();
|
||||
// Detect section by text search
|
||||
const found = source.includes(sectionName);
|
||||
if (!found) {
|
||||
this.attach(`Looking for section: "${sectionName}"`, 'text/plain');
|
||||
this.attach(`Rendered text: ${this.getRenderedText().substring(0, 500)}...`, 'text/plain');
|
||||
this.attach(`Rendered text: ${source.substring(0, 500)}...`, 'text/plain');
|
||||
}
|
||||
expect(hasSection, `Section "${sectionName}" should be visible on screen`).to.be.true;
|
||||
expect(found, `Section "${sectionName}" should be visible on screen`).to.be.true;
|
||||
});
|
||||
|
||||
Then('la page affiche {int} éléments', async function (this: FestipodWorld, count: number) {
|
||||
// This is harder to verify without specific selectors, so we just log it
|
||||
this.attach(`Expected ${count} elements displayed`, 'text/plain');
|
||||
// Cannot count rendered elements without browser automation
|
||||
this.attach(`CANNOT TEST: Counting ${count} elements requires browser automation`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('je peux voir mon profil', async function (this: FestipodWorld) {
|
||||
expect(['profile', 'user-profile']).to.include(this.currentScreenId);
|
||||
// Verify profile content
|
||||
const hasProfileContent = this.hasText('profil') || this.hasText('Profil');
|
||||
expect(hasProfileContent, 'Profile page should display profile content').to.be.true;
|
||||
expect(this.currentScreenId).to.equal('profile');
|
||||
const source = this.getRenderedText();
|
||||
// ProfileScreen.tsx has: <Avatar initials="MD" size="lg" />, <Title>Marie Dupont</Title>, @mariedupont
|
||||
expect(/<Avatar[^>]*initials="MD"[^>]*size="lg"/.test(source), 'Profile should have Avatar with initials="MD" and size="lg"').to.be.true;
|
||||
expect(/<Title[^>]*>Marie Dupont<\/Title>/.test(source), 'Profile should have Title "Marie Dupont"').to.be.true;
|
||||
expect(/@mariedupont/.test(source), 'Profile should have username @mariedupont').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux voir le profil de l\'utilisateur', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('user-profile');
|
||||
const hasProfileContent = this.hasText('Profil') || this.hasText('@');
|
||||
expect(hasProfileContent, 'User profile should display profile information').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
// UserProfileScreen.tsx has: <Avatar initials="JD" size="lg" />, <Title>Jean Durand</Title>, @jeandurand
|
||||
expect(/<Avatar[^>]*initials="JD"[^>]*size="lg"/.test(source), 'User profile should have Avatar with initials="JD" and size="lg"').to.be.true;
|
||||
expect(/<Title[^>]*>Jean Durand<\/Title>/.test(source), 'User profile should have Title "Jean Durand"').to.be.true;
|
||||
expect(/@jeandurand/.test(source), 'User profile should have username @jeandurand').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux voir la liste des événements', async function (this: FestipodWorld) {
|
||||
expect(['events', 'home', 'profile']).to.include(this.currentScreenId);
|
||||
// Verify events list is shown
|
||||
const hasEvents = this.hasText('Événement') || this.hasText('événement') || this.hasText('inscrits');
|
||||
expect(hasEvents, 'Page should display events list').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
if (this.currentScreenId === 'home') {
|
||||
// HomeScreen.tsx has: "Événements à venir" text and EventCard components
|
||||
expect(/Événements à venir/.test(source), 'Home screen should have "Événements à venir" text').to.be.true;
|
||||
} else if (this.currentScreenId === 'events') {
|
||||
// EventsScreen.tsx has: EventCard components with event data
|
||||
expect(/<Card[^>]*onClick/.test(source), 'Events screen should have clickable Card components').to.be.true;
|
||||
} else {
|
||||
this.attach(`UNEXPECTED SCREEN: "${this.currentScreenId}" is not expected to show events list`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
Then('je peux voir le QR code', async function (this: FestipodWorld) {
|
||||
expect(['profile', 'share-profile', 'meeting-points']).to.include(this.currentScreenId);
|
||||
// Check for QR code related content
|
||||
const hasQRContent = this.hasText('QR') || this.hasText('Partager') || this.hasText('partager');
|
||||
expect(hasQRContent, 'Page should have QR code or share functionality').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
if (this.currentScreenId === 'share-profile') {
|
||||
// ShareProfileScreen.tsx has: "QR Code" comment and "Scannez pour me retrouver" text
|
||||
expect(/QR Code/.test(source), 'Share profile should have "QR Code" text').to.be.true;
|
||||
expect(/Scannez pour me retrouver/.test(source), 'Share profile should have "Scannez pour me retrouver" text').to.be.true;
|
||||
} else if (this.currentScreenId === 'meeting-points') {
|
||||
// MeetingPointsScreen.tsx has: "Mon QR Code" text and "Scannez pour m'ajouter"
|
||||
expect(/Mon QR Code/.test(source), 'Meeting points should have "Mon QR Code" text').to.be.true;
|
||||
expect(/Scannez pour m'ajouter/.test(source), 'Meeting points should have "Scannez pour m\'ajouter" text').to.be.true;
|
||||
} else {
|
||||
// QR code is NOT on this screen
|
||||
this.attach(`NOT ON THIS SCREEN: QR code is on share-profile or meeting-points, not "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
Then('je peux voir le lien de partage', async function (this: FestipodWorld) {
|
||||
expect(['profile', 'share-profile']).to.include(this.currentScreenId);
|
||||
const hasShareLink = this.hasText('Partager') || this.hasText('partager') || this.hasText('lien');
|
||||
expect(hasShareLink, 'Page should display share link functionality').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
if (this.currentScreenId === 'share-profile') {
|
||||
// ShareProfileScreen.tsx has: "Mon lien de profil" text and profileLink variable
|
||||
expect(/Mon lien de profil/.test(source), 'Share profile should have "Mon lien de profil" text').to.be.true;
|
||||
expect(/festipod\.app\/u\//.test(source), 'Share profile should have profile link URL').to.be.true;
|
||||
} else {
|
||||
// Share link is NOT on this screen
|
||||
this.attach(`NOT ON THIS SCREEN: Share link is on share-profile, not "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
Given('un événement existe avec les données:', async function (this: FestipodWorld, dataTable) {
|
||||
// Cannot set up test data without backend/database
|
||||
const eventData = dataTable.rowsHash();
|
||||
this.attach(`Event data: ${JSON.stringify(eventData)}`, 'text/plain');
|
||||
this.attach(`CANNOT TEST: Setting up event data requires backend: ${JSON.stringify(eventData)}`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Given('un utilisateur existe avec les données:', async function (this: FestipodWorld, dataTable) {
|
||||
// Cannot set up test data without backend/database
|
||||
const userData = dataTable.rowsHash();
|
||||
this.attach(`User data: ${JSON.stringify(userData)}`, 'text/plain');
|
||||
this.attach(`CANNOT TEST: Setting up user data requires backend: ${JSON.stringify(userData)}`, 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Given('je visualise l\'événement {string}', async function (this: FestipodWorld, eventName: string) {
|
||||
@@ -90,122 +130,119 @@ Given('je visualise le profil de {string}', async function (this: FestipodWorld,
|
||||
|
||||
Then('l\'écran affiche les informations de l\'événement', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('event-detail');
|
||||
// Verify actual content is rendered
|
||||
const expectedContent = screenExpectedContent['event-detail'] || [];
|
||||
const renderedText = this.getRenderedText();
|
||||
|
||||
let foundCount = 0;
|
||||
for (const content of expectedContent) {
|
||||
if (renderedText.includes(content)) {
|
||||
foundCount++;
|
||||
}
|
||||
}
|
||||
|
||||
expect(foundCount, `At least one expected content item should be present`).to.be.greaterThan(0);
|
||||
const source = this.getRenderedText();
|
||||
// EventDetailScreen.tsx has: <Title>, 📅, 🕓, 📍 emojis, and "À propos" section
|
||||
expect(/<Title[^>]*>[^<]+<\/Title>/.test(source), 'Event detail should have a Title').to.be.true;
|
||||
expect(/📅/.test(source), 'Event detail should have date emoji 📅').to.be.true;
|
||||
expect(/🕓/.test(source), 'Event detail should have time emoji 🕓').to.be.true;
|
||||
expect(/📍/.test(source), 'Event detail should have location emoji 📍').to.be.true;
|
||||
expect(/À propos/.test(source), 'Event detail should have "À propos" section').to.be.true;
|
||||
});
|
||||
|
||||
Then('l\'écran affiche les informations du profil', async function (this: FestipodWorld) {
|
||||
expect(['profile', 'user-profile']).to.include(this.currentScreenId);
|
||||
// Verify profile info is rendered
|
||||
const hasProfileInfo = this.hasText('Profil') || this.hasText('@') || this.hasText('Événement');
|
||||
expect(hasProfileInfo, 'Profile information should be displayed').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
if (this.currentScreenId === 'profile') {
|
||||
// ProfileScreen.tsx has: <Avatar initials="MD" size="lg" />, <Title>Marie Dupont</Title>, @mariedupont
|
||||
expect(/<Avatar[^>]*initials="MD"/.test(source), 'Profile should have Avatar with initials="MD"').to.be.true;
|
||||
expect(/<Title[^>]*>Marie Dupont<\/Title>/.test(source), 'Profile should have Title "Marie Dupont"').to.be.true;
|
||||
expect(/@mariedupont/.test(source), 'Profile should have username @mariedupont').to.be.true;
|
||||
} else if (this.currentScreenId === 'user-profile') {
|
||||
// UserProfileScreen.tsx has: <Avatar initials="JD" size="lg" />, <Title>Jean Durand</Title>, @jeandurand
|
||||
expect(/<Avatar[^>]*initials="JD"/.test(source), 'User profile should have Avatar with initials="JD"').to.be.true;
|
||||
expect(/<Title[^>]*>Jean Durand<\/Title>/.test(source), 'User profile should have Title "Jean Durand"').to.be.true;
|
||||
expect(/@jeandurand/.test(source), 'User profile should have username @jeandurand').to.be.true;
|
||||
} else {
|
||||
expect.fail(`Unexpected screen "${this.currentScreenId}" for profile info check`);
|
||||
}
|
||||
});
|
||||
|
||||
Then('je peux ajouter un commentaire', async function (this: FestipodWorld) {
|
||||
// Check for comment feature using precise detector
|
||||
const hasCommentFeature = this.hasField('Commentaire');
|
||||
|
||||
if (!hasCommentFeature) {
|
||||
this.attach(`MISSING FEATURE: Comment functionality is not implemented in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
this.attach(`Expected: textarea element or "commentaire" text in the screen`, 'text/plain');
|
||||
return 'pending'; // Mark as pending instead of failing
|
||||
}
|
||||
// EventDetailScreen.tsx does NOT have comment functionality (no textarea, no "commentaire" text)
|
||||
// This feature is NOT implemented in the UI
|
||||
this.attach('NOT IMPLEMENTED: Comment functionality not in EventDetailScreen.tsx', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('je peux ajouter une note', async function (this: FestipodWorld) {
|
||||
// Check for note feature - similar to comment
|
||||
const hasNoteFeature = this.hasText('Note') || this.hasText('note') || this.hasElement('textarea');
|
||||
|
||||
if (!hasNoteFeature) {
|
||||
this.attach(`MISSING FEATURE: Note functionality is not implemented in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
// No screen has note functionality implemented
|
||||
// This feature is NOT implemented in the UI
|
||||
this.attach('NOT IMPLEMENTED: Note functionality not implemented in any screen', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('je peux filtrer les événements par période', async function (this: FestipodWorld) {
|
||||
// Check for period filter feature
|
||||
const hasPeriodFilter = this.hasText('mois') || this.hasText('trimestre') || this.hasText('année') ||
|
||||
this.hasText('période') || this.hasText('Période');
|
||||
|
||||
if (!hasPeriodFilter) {
|
||||
this.attach(`MISSING FEATURE: Period filter is not implemented in screen "${this.currentScreenId}"`, 'text/plain');
|
||||
return 'pending';
|
||||
}
|
||||
// EventsScreen.tsx has filter badges (Tous, Cette semaine, Proches, Amis) but NOT period filter (mois/trimestre/année)
|
||||
// This feature is NOT implemented in the UI
|
||||
this.attach('NOT IMPLEMENTED: Period filter (mois/trimestre/année) not in EventsScreen.tsx', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('je peux modifier un commentaire', async function (this: FestipodWorld) {
|
||||
// Comment editing is typically available where adding is
|
||||
const hasEditFeature = this.hasText('Modifier') || this.hasText('modifier') || this.hasElement('button');
|
||||
expect(hasEditFeature, 'Edit functionality should be available').to.be.true;
|
||||
// No comment edit functionality exists in any screen
|
||||
// This feature is NOT implemented in the UI
|
||||
this.attach('NOT IMPLEMENTED: Comment edit functionality not implemented', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('je peux supprimer un commentaire', async function (this: FestipodWorld) {
|
||||
// Delete is typically available where edit is
|
||||
const hasDeleteFeature = this.hasText('Supprimer') || this.hasText('supprimer') || this.hasElement('button');
|
||||
expect(hasDeleteFeature, 'Delete functionality should be available').to.be.true;
|
||||
// No comment delete functionality exists in any screen
|
||||
// This feature is NOT implemented in the UI
|
||||
this.attach('NOT IMPLEMENTED: Comment delete functionality not implemented', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('je peux m\'inscrire à l\'événement', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('event-detail');
|
||||
// Check for registration button
|
||||
const hasRegisterFeature = this.hasText('inscription') || this.hasText('Participer') ||
|
||||
this.hasText('participer') || this.hasText('S\'inscrire') ||
|
||||
this.hasText('Rejoindre');
|
||||
expect(hasRegisterFeature, 'Registration feature should be available').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
// EventDetailScreen.tsx line 49: {isJoined ? '✓ Inscrit' : 'Participer'}
|
||||
// The button shows "Participer" when not joined
|
||||
const hasParticiperButton = /isJoined \? '✓ Inscrit' : 'Participer'/.test(source);
|
||||
expect(hasParticiperButton, 'Event detail should have Participer/Inscrit toggle button').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux me désinscrire de l\'événement', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('event-detail');
|
||||
// Unregister is typically on the same page as register
|
||||
const hasUnregisterFeature = this.hasText('désinscri') || this.hasText('Annuler') ||
|
||||
this.hasText('Quitter') || this.hasElement('button');
|
||||
expect(hasUnregisterFeature, 'Unregister feature should be available').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
// EventDetailScreen.tsx line 49: {isJoined ? '✓ Inscrit' : 'Participer'}
|
||||
// Same button toggles - clicking "✓ Inscrit" will unregister
|
||||
const hasInscritButton = /isJoined \? '✓ Inscrit' : 'Participer'/.test(source);
|
||||
expect(hasInscritButton, 'Event detail should have Participer/Inscrit toggle button (click to unregister)').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux contacter l\'utilisateur', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('user-profile');
|
||||
// Check for contact functionality
|
||||
const hasContactFeature = this.hasText('Contact') || this.hasText('Message') ||
|
||||
this.hasText('message') || this.hasElement('button');
|
||||
expect(hasContactFeature, 'Contact feature should be available').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
// UserProfileScreen.tsx line 44: <Button>Contacter</Button>
|
||||
const hasContactButton = /<Button>Contacter<\/Button>/.test(source);
|
||||
expect(hasContactButton, 'User profile should have "Contacter" button').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux voir les événements auxquels l\'utilisateur a participé', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('user-profile');
|
||||
// Check for user's events
|
||||
const hasUserEvents = this.hasText('Événement') || this.hasText('événement') ||
|
||||
this.hasText('Participation') || this.hasText('participation');
|
||||
expect(hasUserEvents, 'User events should be visible').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
// UserProfileScreen.tsx line 52: "Événements en commun" section with pastEvents
|
||||
expect(/Événements en commun/.test(source), 'User profile should have "Événements en commun" section').to.be.true;
|
||||
expect(/pastEvents/.test(source), 'User profile should display pastEvents data').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux configurer mes notifications', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('settings');
|
||||
// Check for notification settings
|
||||
const hasNotificationSetting = this.hasText('Notification') || this.hasText('notification');
|
||||
expect(hasNotificationSetting, 'Notification settings should be visible').to.be.true;
|
||||
const source = this.getRenderedText();
|
||||
// SettingsScreen.tsx line 25: <Text>Notifications</Text> with Toggle
|
||||
expect(/>Notifications</.test(source), 'Settings should have "Notifications" text').to.be.true;
|
||||
expect(/<Toggle[^>]*checked=\{notifications\}/.test(source), 'Settings should have Toggle for notifications').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux définir mon rayon de notification', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('settings');
|
||||
// Check for location/radius setting
|
||||
const hasRadiusSetting = this.hasText('Localisation') || this.hasText('localisation') ||
|
||||
this.hasText('rayon') || this.hasText('Rayon');
|
||||
expect(hasRadiusSetting, 'Location/radius setting should be visible').to.be.true;
|
||||
// SettingsScreen.tsx has "Localisation" toggle but NOT "rayon" or "km" setting
|
||||
// This feature is NOT implemented in the UI
|
||||
this.attach('NOT IMPLEMENTED: Radius setting (rayon/km) is not in SettingsScreen.tsx', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
Then('je peux définir mes thématiques d\'intérêt', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('settings');
|
||||
// Settings page should allow configuring interests (or it could be on profile)
|
||||
// For now just verify we're on settings
|
||||
expect(this.currentScreen, 'Settings screen should be loaded').to.not.be.null;
|
||||
// SettingsScreen.tsx does NOT have thematic/interest settings
|
||||
// This feature is NOT implemented in the UI
|
||||
this.attach('NOT IMPLEMENTED: Thematic/interest settings not in SettingsScreen.tsx', 'text/plain');
|
||||
return 'pending';
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ export const screenFieldDetectors: Record<string, Record<string, (source: string
|
||||
// EventDetailScreen.tsx lines 77-81: À propos section with description
|
||||
'Description': (s) => {
|
||||
const match = s.match(/À propos[\s\S]*?<Text[^>]*>([\s\S]*?)<\/Text>/);
|
||||
return match !== null && match[1].trim().length > 50;
|
||||
return match !== null && match[1] !== undefined && match[1].trim().length > 50;
|
||||
},
|
||||
// EventDetailScreen.tsx lines 8-13: attendees with { name: 'Marie' } rendered via {a.name}
|
||||
'Nom': (s) => /name:\s*['"][^'"]+['"]/.test(s) && /\{[^}]*\.name\}/.test(s),
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
"dev": "bun --hot src/index.ts",
|
||||
"start": "NODE_ENV=production bun src/index.ts",
|
||||
"build": "bun run build.ts",
|
||||
"test:cucumber": "bun run cucumber:run && bun run cucumber:report",
|
||||
"test:cucumber": "bun run cucumber:run && bun run cucumber:report && bun run features:parse && bun run steps:extract",
|
||||
"cucumber:run": "node --import tsx/esm node_modules/.bin/cucumber-js --config cucumber.json",
|
||||
"cucumber:report": "bun scripts/parse-test-results.ts",
|
||||
"features:parse": "bun scripts/parse-features.ts",
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1093
-1063
File diff suppressed because it is too large
Load Diff
@@ -395,8 +395,9 @@ interface StepRendererProps {
|
||||
|
||||
function StepRenderer({ step, showDefinitions }: StepRendererProps) {
|
||||
// Always check for step definition to show dotted underline
|
||||
// Use step.text (without keyword) to match against step definition patterns
|
||||
const stepDef = step.type !== 'table' && step.type !== 'other' && step.type !== 'examples'
|
||||
? findStepDefinition(step.originalLine.trim())
|
||||
? findStepDefinition(step.text)
|
||||
: null;
|
||||
|
||||
// Keyword colors
|
||||
|
||||
@@ -194,7 +194,7 @@ export const parsedFeatures: ParsedFeature[] = [
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Vérifier la présence du bouton d'annulation",
|
||||
"name": "Pouvoir annuler la création d'événement",
|
||||
"tags": [],
|
||||
"steps": [
|
||||
{
|
||||
@@ -203,13 +203,13 @@ export const parsedFeatures: ParsedFeature[] = [
|
||||
},
|
||||
{
|
||||
"keyword": "Alors",
|
||||
"text": "l'écran contient une section \"Annuler\""
|
||||
"text": "je peux annuler et revenir à l'écran précédent"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"filePath": "features/event/us-13-creer-evenement.feature",
|
||||
"rawContent": "# language: fr\n@EVENT @priority-1\nFonctionnalité: US-13 Créer/Modifier/Supprimer un événement\n En tant qu'utilisateur\n Je peux créer/modifier/supprimer un événement\n En choisissant les dates, horaires, lieu et thématique\n Afin de créer/présenter le contenu de cet événement et le catégoriser\n\n Contexte:\n Étant donné je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder à la création d'événement\n Étant donné je suis sur la page \"accueil\"\n Quand je navigue vers \"créer un événement\"\n Alors je vois l'écran \"create-event\"\n\n Scénario: Vérifier les champs obligatoires du formulaire\n Étant donné l'écran \"create-event\" est affiché\n Alors le formulaire contient les champs obligatoires suivants:\n | Nom de l'événement |\n | Date |\n | Heure de début |\n | Lieu |\n | Thématique |\n\n Scénario: Remplir le formulaire de création d'événement\n Étant donné je suis sur la page \"créer un événement\"\n Quand je remplis le champ \"Nom de l'événement\" avec \"Mon événement\"\n Et je remplis le champ \"Date\" avec \"2025-02-15\"\n Et je remplis le champ \"Heure de début\" avec \"14:00\"\n Et je remplis le champ \"Lieu\" avec \"Lyon\"\n Et je remplis le champ \"Thématique\" avec \"Technologie\"\n Alors le champ \"Nom de l'événement\" affiche \"Mon événement\"\n Et le champ \"Lieu\" affiche \"Lyon\"\n\n Scénario: Vérifier la présence du bouton de création\n Étant donné je suis sur la page \"créer un événement\"\n Alors l'écran contient une section \"Créer l'événement\"\n\n Scénario: Vérifier la présence du bouton d'annulation\n Étant donné je suis sur la page \"créer un événement\"\n Alors l'écran contient une section \"Annuler\"\n"
|
||||
"rawContent": "# language: fr\n@EVENT @priority-1\nFonctionnalité: US-13 Créer/Modifier/Supprimer un événement\n En tant qu'utilisateur\n Je peux créer/modifier/supprimer un événement\n En choisissant les dates, horaires, lieu et thématique\n Afin de créer/présenter le contenu de cet événement et le catégoriser\n\n Contexte:\n Étant donné je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder à la création d'événement\n Étant donné je suis sur la page \"accueil\"\n Quand je navigue vers \"créer un événement\"\n Alors je vois l'écran \"create-event\"\n\n Scénario: Vérifier les champs obligatoires du formulaire\n Étant donné l'écran \"create-event\" est affiché\n Alors le formulaire contient les champs obligatoires suivants:\n | Nom de l'événement |\n | Date |\n | Heure de début |\n | Lieu |\n | Thématique |\n\n Scénario: Remplir le formulaire de création d'événement\n Étant donné je suis sur la page \"créer un événement\"\n Quand je remplis le champ \"Nom de l'événement\" avec \"Mon événement\"\n Et je remplis le champ \"Date\" avec \"2025-02-15\"\n Et je remplis le champ \"Heure de début\" avec \"14:00\"\n Et je remplis le champ \"Lieu\" avec \"Lyon\"\n Et je remplis le champ \"Thématique\" avec \"Technologie\"\n Alors le champ \"Nom de l'événement\" affiche \"Mon événement\"\n Et le champ \"Lieu\" affiche \"Lyon\"\n\n Scénario: Vérifier la présence du bouton de création\n Étant donné je suis sur la page \"créer un événement\"\n Alors l'écran contient une section \"Créer l'événement\"\n\n Scénario: Pouvoir annuler la création d'événement\n Étant donné je suis sur la page \"créer un événement\"\n Alors je peux annuler et revenir à l'écran précédent\n"
|
||||
},
|
||||
{
|
||||
"id": "us-3",
|
||||
@@ -971,7 +971,7 @@ export const parsedFeatures: ParsedFeature[] = [
|
||||
}
|
||||
],
|
||||
"filePath": "features/notif/us-19-recapitulatif.feature",
|
||||
"rawContent": "# language: fr\n@NOTIF @priority-2\nFonctionnalité: US-19 Recevoir un récapitulatif des prochaines rencontres\n En tant qu'utilisateur\n Je peux recevoir un récapitulatif des prochaines rencontres\n En réceptionnant une liste des événements auxquels je suis inscrit ou qui sont proches de chez moi\n Afin d'établir un programme des événements auxquels je participe par période\n\n Contexte:\n Étant donné je suis connecté en tant qu'utilisateur\n\n Scénario: Voir les événements à venir sur l'accueil\n Étant donné je suis sur la page \"accueil\"\n Alors l'écran contient une section \"Événements à venir\"\n\n @pending\n Scénario: Voir le récapitulatif par période\n Étant donné je suis sur la page \"accueil\"\n Alors je peux filtrer les événements par période\n\n Scénario: Voir les événements proches géographiquement\n Étant donné je suis sur la page \"accueil\"\n Alors l'écran contient une section \"Près de chez moi\"\n\n Scénario: Voir mes inscriptions\n Étant donné je suis sur la page \"accueil\"\n Alors l'écran contient une section \"Mes inscriptions\"\n\n Scénario: Vérifier les données de l'accueil\n Étant donné l'écran \"home\" est affiché\n Alors le formulaire contient les champs obligatoires suivants:\n | Événements à venir |\n | Navigation |\n"
|
||||
"rawContent": "# language: fr\n# Note: US-19 concerne les récapitulatifs par email - non testable via écrans\n# Les scénarios ci-dessous testent l'affichage sur l'écran d'accueil (aspect UI)\n@NOTIF @priority-2\nFonctionnalité: US-19 Recevoir un récapitulatif des prochaines rencontres\n En tant qu'utilisateur\n Je peux recevoir un récapitulatif des prochaines rencontres\n En réceptionnant une liste des événements auxquels je suis inscrit ou qui sont proches de chez moi\n Afin d'établir un programme des événements auxquels je participe par période\n\n Contexte:\n Étant donné je suis connecté en tant qu'utilisateur\n\n Scénario: Voir les événements à venir sur l'accueil\n Étant donné je suis sur la page \"accueil\"\n Alors l'écran contient une section \"Événements à venir\"\n\n @pending\n Scénario: Voir le récapitulatif par période\n Étant donné je suis sur la page \"accueil\"\n Alors je peux filtrer les événements par période\n\n @pending\n Scénario: Voir les événements proches géographiquement\n Étant donné je suis sur la page \"accueil\"\n Alors l'écran contient une section \"Près de chez moi\"\n\n @pending\n Scénario: Voir mes inscriptions\n Étant donné je suis sur la page \"accueil\"\n Alors l'écran contient une section \"Mes inscriptions\"\n\n @pending\n Scénario: Vérifier les données de l'accueil\n Étant donné l'écran \"home\" est affiché\n Alors le formulaire contient les champs obligatoires suivants:\n | Événements à venir |\n | Navigation |\n"
|
||||
},
|
||||
{
|
||||
"id": "us-17",
|
||||
@@ -1078,7 +1078,7 @@ export const parsedFeatures: ParsedFeature[] = [
|
||||
}
|
||||
],
|
||||
"filePath": "features/notif/us-17-informer-utilisateurs.feature",
|
||||
"rawContent": "# language: fr\n@NOTIF @priority-2\nFonctionnalité: US-17 Informer automatiquement d'autres utilisateurs\n En tant qu'utilisateur\n Je peux informer automatiquement d'autres utilisateurs de ma participation à un événement\n En utilisant un système de notifications pour transmettre le lien de l'événement\n Afin d'informer les utilisateurs proches, intéressés par la thématique, ou mes abonnés\n\n Contexte:\n Étant donné je suis connecté en tant qu'utilisateur\n\n Scénario: Partager un événement auquel je participe\n Étant donné je suis sur la page \"détail événement\"\n Quand je clique sur \"Partager\"\n Alors l'écran contient une section \"Options de partage\"\n\n Scénario: Informer les utilisateurs à proximité\n Étant donné je suis sur la page \"détail événement\"\n Quand je clique sur \"Notifier à proximité\"\n Alors l'écran contient une section \"Rayon de notification\"\n\n Scénario: Informer les utilisateurs par thématique\n Étant donné je suis sur la page \"détail événement\"\n Quand je clique sur \"Notifier par thématique\"\n Alors l'écran contient une section \"Thématiques\"\n\n Scénario: Informer mes abonnés\n Étant donné je suis sur la page \"détail événement\"\n Quand je clique sur \"Notifier mes abonnés\"\n Alors l'écran contient une section \"Mes abonnés\"\n\n Scénario: Combiner les options de notification\n Étant donné je suis sur la page \"détail événement\"\n Alors l'écran contient une section \"Options de notification\"\n"
|
||||
"rawContent": "# language: fr\n# Note: US-17 concerne les notifications par email - non testable via écrans\n@NOTIF @priority-2\nFonctionnalité: US-17 Informer automatiquement d'autres utilisateurs\n En tant qu'utilisateur\n Je peux informer automatiquement d'autres utilisateurs de ma participation à un événement\n En utilisant un système de notifications pour transmettre le lien de l'événement\n Afin d'informer les utilisateurs proches, intéressés par la thématique, ou mes abonnés\n\n Contexte:\n Étant donné je suis connecté en tant qu'utilisateur\n\n @pending\n Scénario: Partager un événement auquel je participe\n Étant donné je suis sur la page \"détail événement\"\n Quand je clique sur \"Partager\"\n Alors l'écran contient une section \"Options de partage\"\n\n @pending\n Scénario: Informer les utilisateurs à proximité\n Étant donné je suis sur la page \"détail événement\"\n Quand je clique sur \"Notifier à proximité\"\n Alors l'écran contient une section \"Rayon de notification\"\n\n @pending\n Scénario: Informer les utilisateurs par thématique\n Étant donné je suis sur la page \"détail événement\"\n Quand je clique sur \"Notifier par thématique\"\n Alors l'écran contient une section \"Thématiques\"\n\n @pending\n Scénario: Informer mes abonnés\n Étant donné je suis sur la page \"détail événement\"\n Quand je clique sur \"Notifier mes abonnés\"\n Alors l'écran contient une section \"Mes abonnés\"\n\n @pending\n Scénario: Combiner les options de notification\n Étant donné je suis sur la page \"détail événement\"\n Alors l'écran contient une section \"Options de notification\"\n"
|
||||
},
|
||||
{
|
||||
"id": "us-18",
|
||||
|
||||
+88
-88
@@ -49,85 +49,85 @@ export const stepDefinitions: StepDefinitionInfo[] = [
|
||||
"pattern": "je clique sur {string}",
|
||||
"keyword": "When",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "When('je clique sur {string}', async function (this: FestipodWorld, elementName: string) {\n this.attach(`Clicked on: ${elementName}`, 'text/plain');\n});",
|
||||
"sourceCode": "When('je clique sur {string}', async function (this: FestipodWorld, elementName: string) {\n const source = this.getRenderedText();\n // Check that a clickable element with this text exists (onClick handler + text content)\n const escapedName = elementName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`onClick[^>]*>[^<]*${escapedName}`, 'i');\n const found = pattern.test(source);\n if (!found) {\n this.attach(`MISSING: Clickable element \"${elementName}\" not found in screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 53
|
||||
},
|
||||
{
|
||||
"pattern": "je sélectionne {string}",
|
||||
"keyword": "When",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "When('je sélectionne {string}', async function (this: FestipodWorld, elementName: string) {\n this.attach(`Selected: ${elementName}`, 'text/plain');\n});",
|
||||
"lineNumber": 57
|
||||
"sourceCode": "When('je sélectionne {string}', async function (this: FestipodWorld, elementName: string) {\n const source = this.getRenderedText();\n // Check that a selectable element with this text exists\n const escapedName = elementName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`onClick[^>]*>[^<]*${escapedName}`, 'i');\n const found = pattern.test(source);\n if (!found) {\n this.attach(`MISSING: Selectable element \"${elementName}\" not found in screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 65
|
||||
},
|
||||
{
|
||||
"pattern": "je clique sur le bouton {string}",
|
||||
"keyword": "When",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "When('je clique sur le bouton {string}', async function (this: FestipodWorld, buttonName: string) {\n this.attach(`Clicked button: ${buttonName}`, 'text/plain');\n});",
|
||||
"lineNumber": 61
|
||||
"sourceCode": "When('je clique sur le bouton {string}', async function (this: FestipodWorld, buttonName: string) {\n const source = this.getRenderedText();\n // Check that a Button component with this label exists\n const escapedName = buttonName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`<Button[^>]*>[^<]*${escapedName}[^<]*</Button>`, 'i');\n const found = pattern.test(source);\n if (!found) {\n this.attach(`MISSING: Button \"${buttonName}\" not found in screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 77
|
||||
},
|
||||
{
|
||||
"pattern": "je clique sur un participant",
|
||||
"keyword": "When",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "When('je clique sur un participant', async function (this: FestipodWorld) {\n this.navigateTo('#/demo/user-profile');\n});",
|
||||
"lineNumber": 65
|
||||
"lineNumber": 89
|
||||
},
|
||||
{
|
||||
"pattern": "je clique sur un événement",
|
||||
"keyword": "When",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "When('je clique sur un événement', async function (this: FestipodWorld) {\n this.navigateTo('#/demo/event-detail');\n});",
|
||||
"lineNumber": 69
|
||||
"lineNumber": 93
|
||||
},
|
||||
{
|
||||
"pattern": "je suis redirigé vers {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('je suis redirigé vers {string}', async function (this: FestipodWorld, pageName: string) {\n const screenId = resolveScreenId(pageName);\n expect(this.currentScreenId).to.equal(screenId);\n});",
|
||||
"lineNumber": 73
|
||||
"lineNumber": 97
|
||||
},
|
||||
{
|
||||
"pattern": "je vois l'écran {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('je vois l\\'écran {string}', async function (this: FestipodWorld, pageName: string) {\n const screenId = resolveScreenId(pageName);\n expect(this.currentScreenId).to.equal(screenId);\n});",
|
||||
"lineNumber": 78
|
||||
"lineNumber": 102
|
||||
},
|
||||
{
|
||||
"pattern": "je reste sur la page {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('je reste sur la page {string}', async function (this: FestipodWorld, pageName: string) {\n const screenId = resolveScreenId(pageName);\n expect(this.currentScreenId).to.equal(screenId);\n});",
|
||||
"lineNumber": 83
|
||||
"lineNumber": 107
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran contient une section {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('l\\'écran contient une section {string}', async function (this: FestipodWorld, sectionName: string) {\n const hasSection = this.hasText(sectionName);\n expect(hasSection, `Section \"${sectionName}\" should be visible on screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 88
|
||||
"sourceCode": "Then('l\\'écran contient une section {string}', async function (this: FestipodWorld, sectionName: string) {\n const found = this.hasText(sectionName);\n if (!found) {\n this.attach(`MISSING SECTION: \"${sectionName}\" not found in screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 112
|
||||
},
|
||||
{
|
||||
"pattern": "je peux annuler et revenir à l'écran précédent",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('je peux annuler et revenir à l\\'écran précédent', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n // CreateEventScreen has a ✕ close button in the header with onClick={() => navigate('home')}\n const source = this.getRenderedText();\n const hasCloseButton = /onClick[^>]*>[^<]*✕/.test(source);\n expect(hasCloseButton, 'Create event screen should have a close button (✕) with navigation action').to.be.true;\n});",
|
||||
"lineNumber": 93
|
||||
"sourceCode": "Then('je peux annuler et revenir à l\\'écran précédent', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n // Detect ✕ close button with onClick handler that calls navigate()\n const found = /onClick\\s*=\\s*\\{\\s*\\(\\)\\s*=>\\s*navigate\\s*\\(['\"]home['\"]\\)\\s*\\}[^>]*>✕</.test(source);\n expect(found, 'Create event screen should have ✕ button with navigate(\"home\")').to.be.true;\n});",
|
||||
"lineNumber": 120
|
||||
},
|
||||
{
|
||||
"pattern": "je peux naviguer vers {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('je peux naviguer vers {string}', async function (this: FestipodWorld, pageName: string) {\n const screenId = resolveScreenId(pageName);\n this.attach(`Navigation available to: ${screenId}`, 'text/plain');\n});",
|
||||
"lineNumber": 101
|
||||
"sourceCode": "Then('je peux naviguer vers {string}', async function (this: FestipodWorld, pageName: string) {\n const screenId = resolveScreenId(pageName);\n const source = this.getRenderedText();\n // Check that a navigation link to this screen exists: navigate('screenId') or onClick={() => navigate('screenId')}\n const pattern = new RegExp(`navigate\\\\s*\\\\(\\\\s*['\"]${screenId}['\"]\\\\s*\\\\)`);\n const found = pattern.test(source);\n if (!found) {\n this.attach(`MISSING: Navigation to \"${screenId}\" not found in screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 128
|
||||
},
|
||||
{
|
||||
"pattern": "la navigation affiche {string} comme actif",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('la navigation affiche {string} comme actif', async function (this: FestipodWorld, menuItem: string) {\n this.attach(`Active menu: ${menuItem}`, 'text/plain');\n});",
|
||||
"lineNumber": 106
|
||||
"sourceCode": "Then('la navigation affiche {string} comme actif', async function (this: FestipodWorld, menuItem: string) {\n const source = this.getRenderedText();\n // Check that NavBar has an item with this label and active: true\n // Pattern: { icon: '...', label: 'menuItem', active: true }\n const escapedItem = menuItem.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`label:\\\\s*['\"]${escapedItem}['\"][^}]*active:\\\\s*true`, 'i');\n const found = pattern.test(source);\n if (!found) {\n this.attach(`MISSING: Menu item \"${menuItem}\" is not active in NavBar of screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 140
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran {string} est affiché",
|
||||
@@ -147,260 +147,260 @@ export const stepDefinitions: StepDefinitionInfo[] = [
|
||||
"pattern": "je remplis le champ {string} avec {string}",
|
||||
"keyword": "When",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "When('je remplis le champ {string} avec {string}', async function (this: FestipodWorld, fieldName: string, value: string) {\n const existing = this.formFields.get(fieldName);\n this.formFields.set(fieldName, {\n required: existing?.required ?? false,\n value\n });",
|
||||
"sourceCode": "When('je remplis le champ {string} avec {string}', async function (this: FestipodWorld, fieldName: string, value: string) {\n // Cannot fill form fields without browser automation\n this.attach(`CANNOT TEST: Filling field \"${fieldName}\" with \"${value}\" requires browser automation`, 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 16
|
||||
},
|
||||
{
|
||||
"pattern": "je laisse le champ {string} vide",
|
||||
"keyword": "When",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "When('je laisse le champ {string} vide', async function (this: FestipodWorld, fieldName: string) {\n const existing = this.formFields.get(fieldName);\n if (existing) {\n this.formFields.set(fieldName, { ...existing, value: '' });",
|
||||
"lineNumber": 24
|
||||
"sourceCode": "When('je laisse le champ {string} vide', async function (this: FestipodWorld, fieldName: string) {\n // Cannot manipulate form fields without browser automation\n this.attach(`CANNOT TEST: Leaving field \"${fieldName}\" empty requires browser automation`, 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 22
|
||||
},
|
||||
{
|
||||
"pattern": "je soumets le formulaire",
|
||||
"keyword": "When",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "When('je soumets le formulaire', async function (this: FestipodWorld) {\n this.attach('Form submitted', 'text/plain');\n});",
|
||||
"lineNumber": 31
|
||||
"sourceCode": "When('je soumets le formulaire', async function (this: FestipodWorld) {\n // Cannot submit forms without browser automation\n this.attach('CANNOT TEST: Form submission requires browser automation', 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 28
|
||||
},
|
||||
{
|
||||
"pattern": "le formulaire contient le champ obligatoire {string}",
|
||||
"keyword": "Then",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Then('le formulaire contient le champ obligatoire {string}', async function (this: FestipodWorld, fieldName: string) {\n const field = this.formFields.get(fieldName);\n expect(field, `Field \"${fieldName}\" should exist`).to.not.be.undefined;\n expect(field?.required, `Field \"${fieldName}\" should be required`).to.equal(true);\n});",
|
||||
"lineNumber": 35
|
||||
"sourceCode": "Then('le formulaire contient le champ obligatoire {string}', async function (this: FestipodWorld, fieldName: string) {\n // This step is for form screens only (create-event)\n // For display screens, use different steps\n if (this.currentScreenId !== 'create-event') {\n this.attach(`WRONG STEP: \"le formulaire contient le champ obligatoire\" is for forms. Screen \"${this.currentScreenId}\" is not a form.`, 'text/plain');\n return 'pending';\n }\n const source = this.getRenderedText();\n // CreateEventScreen.tsx: Required fields have \" *\" after label: >Label *<\n const escapedName = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`>${escapedName}\\\\s*\\\\*<`);\n expect(pattern.test(source), `Field \"${fieldName}\" should be marked as required (with *) in create-event screen`).to.be.true;\n});",
|
||||
"lineNumber": 34
|
||||
},
|
||||
{
|
||||
"pattern": "le formulaire contient les champs obligatoires suivants:",
|
||||
"keyword": "Then",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Then('le formulaire contient les champs obligatoires suivants:', async function (this: FestipodWorld, dataTable) {\n const expectedFields = dataTable.raw().flat();\n expectedFields.forEach((fieldName: string) => {\n const field = this.formFields.get(fieldName);\n expect(field, `Field \"${fieldName}\" should exist`).to.not.be.undefined;\n expect(field?.required, `Field \"${fieldName}\" should be required`).to.equal(true);\n });",
|
||||
"lineNumber": 41
|
||||
"sourceCode": "Then('le formulaire contient les champs obligatoires suivants:', async function (this: FestipodWorld, dataTable) {\n // This step is for form screens only (create-event)\n // For display screens, use different steps\n if (this.currentScreenId !== 'create-event') {\n this.attach(`WRONG STEP: \"le formulaire contient les champs obligatoires\" is for forms. Screen \"${this.currentScreenId}\" is not a form.`, 'text/plain');\n return 'pending';\n }\n const source = this.getRenderedText();\n const expectedFields = dataTable.raw().flat();\n expectedFields.forEach((fieldName: string) => {\n // CreateEventScreen.tsx: Required fields have \" *\" after label: >Label *<\n const escapedName = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`>${escapedName}\\\\s*\\\\*<`);\n expect(pattern.test(source), `Field \"${fieldName}\" should be marked as required (with *) in create-event screen`).to.be.true;\n });",
|
||||
"lineNumber": 48
|
||||
},
|
||||
{
|
||||
"pattern": "le champ {string} est facultatif",
|
||||
"keyword": "Then",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Then('le champ {string} est facultatif', async function (this: FestipodWorld, fieldName: string) {\n const field = this.formFields.get(fieldName);\n if (field) {\n expect(field.required).to.equal(false);\n }\n});",
|
||||
"lineNumber": 50
|
||||
"sourceCode": "Then('le champ {string} est facultatif', async function (this: FestipodWorld, fieldName: string) {\n const source = this.getRenderedText();\n // Optional fields have label without \" *\": >Label< followed by Input\n const escapedName = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n // Check field exists but NOT marked as required\n const existsPattern = new RegExp(`>${escapedName}<`);\n const requiredPattern = new RegExp(`>${escapedName}\\\\s*\\\\*<`);\n expect(existsPattern.test(source), `Field \"${fieldName}\" should exist in screen`).to.be.true;\n expect(requiredPattern.test(source), `Field \"${fieldName}\" should NOT be marked as required`).to.be.false;\n});",
|
||||
"lineNumber": 65
|
||||
},
|
||||
{
|
||||
"pattern": "le champ {string} affiche {string}",
|
||||
"keyword": "Then",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Then('le champ {string} affiche {string}', async function (this: FestipodWorld, fieldName: string, expectedValue: string) {\n const field = this.formFields.get(fieldName);\n expect(field?.value).to.equal(expectedValue);\n});",
|
||||
"lineNumber": 57
|
||||
"sourceCode": "Then('le champ {string} affiche {string}', async function (this: FestipodWorld, fieldName: string, expectedValue: string) {\n // Cannot verify displayed field values without browser automation\n this.attach(`CANNOT TEST: Verifying field \"${fieldName}\" displays \"${expectedValue}\" requires browser automation`, 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 76
|
||||
},
|
||||
{
|
||||
"pattern": "le champ {string} est présent",
|
||||
"keyword": "Then",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Then('le champ {string} est présent', async function (this: FestipodWorld, fieldName: string) {\n const field = this.formFields.get(fieldName);\n expect(field, `Field \"${fieldName}\" should exist`).to.not.be.undefined;\n});",
|
||||
"lineNumber": 62
|
||||
"sourceCode": "Then('le champ {string} est présent', async function (this: FestipodWorld, fieldName: string) {\n const source = this.getRenderedText();\n // Check that field label exists in screen source\n const escapedName = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`>${escapedName}[^<]*<`);\n const found = pattern.test(source);\n if (!found) {\n this.attach(`NOT FOUND: Field \"${fieldName}\" not present in screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 82
|
||||
},
|
||||
{
|
||||
"pattern": "une erreur de validation est affichée pour {string}",
|
||||
"keyword": "Then",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Then('une erreur de validation est affichée pour {string}', async function (this: FestipodWorld, fieldName: string) {\n const field = this.formFields.get(fieldName);\n expect(field?.required).to.equal(true);\n expect(field?.value).to.equal('');\n this.attach(`Validation error for: ${fieldName}`, 'text/plain');\n});",
|
||||
"lineNumber": 67
|
||||
"sourceCode": "Then('une erreur de validation est affichée pour {string}', async function (this: FestipodWorld, fieldName: string) {\n // Cannot verify validation errors without browser automation\n this.attach(`CANNOT TEST: Validation error for \"${fieldName}\" requires browser automation`, 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 94
|
||||
},
|
||||
{
|
||||
"pattern": "le formulaire affiche {int} champs",
|
||||
"keyword": "Then",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Then('le formulaire affiche {int} champs', async function (this: FestipodWorld, count: number) {\n expect(this.formFields.size).to.equal(count);\n});",
|
||||
"lineNumber": 74
|
||||
"sourceCode": "Then('le formulaire affiche {int} champs', async function (this: FestipodWorld, count: number) {\n // Cannot count form fields without specific analysis\n this.attach(`CANNOT TEST: Counting ${count} form fields requires more specific screen analysis`, 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 100
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir la liste des participants",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir la liste des participants', async function (this: FestipodWorld) {\n const screensWithParticipants = ['event-detail', 'participants-list', 'invite'];\n expect(screensWithParticipants, `Screen ${this.currentScreenId} should show participants`).to.include(this.currentScreenId);\n\n // Verify the text \"Participant\" appears in the rendered content\n const hasParticipants = this.hasText('Participant') || this.hasText('participant') || this.hasText('inscrits');\n expect(hasParticipants, 'Page should display participants list').to.be.true;\n});",
|
||||
"lineNumber": 6
|
||||
"sourceCode": "Then('je peux voir la liste des participants', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n const source = this.getRenderedText();\n // EventDetailScreen.tsx has: <Avatar components and \"Participants (12)\" text\n const hasAvatars = /<Avatar/.test(source);\n const hasParticipantsSection = /Participants\\s*\\(\\d+\\)/.test(source);\n expect(hasAvatars, 'Event detail should have Avatar components for participants').to.be.true;\n expect(hasParticipantsSection, 'Event detail should have \"Participants (N)\" section').to.be.true;\n});",
|
||||
"lineNumber": 5
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir les détails de l'événement",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir les détails de l\\'événement', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n // Verify event detail content is rendered\n const hasEventInfo = this.hasText('Description') || this.hasText('Participant') || this.hasText('inscrits');\n expect(hasEventInfo, 'Event detail page should show event information').to.be.true;\n});",
|
||||
"sourceCode": "Then('je peux voir les détails de l\\'événement', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n const source = this.getRenderedText();\n // EventDetailScreen.tsx has: <Title>, 📅, 🕓, 📍 emojis, and \"À propos\" section\n expect(/<Title[^>]*>[^<]+<\\/Title>/.test(source), 'Event detail should have a Title').to.be.true;\n expect(/📅/.test(source), 'Event detail should have date emoji 📅').to.be.true;\n expect(/🕓/.test(source), 'Event detail should have time emoji 🕓').to.be.true;\n expect(/📍/.test(source), 'Event detail should have location emoji 📍').to.be.true;\n expect(/À propos/.test(source), 'Event detail should have \"À propos\" section').to.be.true;\n});",
|
||||
"lineNumber": 15
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir la section {string}",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir la section {string}', async function (this: FestipodWorld, sectionName: string) {\n const hasSection = this.hasText(sectionName);\n if (!hasSection) {\n this.attach(`Looking for section: \"${sectionName}\"`, 'text/plain');\n this.attach(`Rendered text: ${this.getRenderedText().substring(0, 500)}...`, 'text/plain');\n }\n expect(hasSection, `Section \"${sectionName}\" should be visible on screen`).to.be.true;\n});",
|
||||
"lineNumber": 22
|
||||
"sourceCode": "Then('je peux voir la section {string}', async function (this: FestipodWorld, sectionName: string) {\n const source = this.getRenderedText();\n // Detect section by text search\n const found = source.includes(sectionName);\n if (!found) {\n this.attach(`Looking for section: \"${sectionName}\"`, 'text/plain');\n this.attach(`Rendered text: ${source.substring(0, 500)}...`, 'text/plain');\n }\n expect(found, `Section \"${sectionName}\" should be visible on screen`).to.be.true;\n});",
|
||||
"lineNumber": 26
|
||||
},
|
||||
{
|
||||
"pattern": "la page affiche {int} éléments",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('la page affiche {int} éléments', async function (this: FestipodWorld, count: number) {\n // This is harder to verify without specific selectors, so we just log it\n this.attach(`Expected ${count} elements displayed`, 'text/plain');\n});",
|
||||
"lineNumber": 31
|
||||
"sourceCode": "Then('la page affiche {int} éléments', async function (this: FestipodWorld, count: number) {\n // Cannot count rendered elements without browser automation\n this.attach(`CANNOT TEST: Counting ${count} elements requires browser automation`, 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 37
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir mon profil",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir mon profil', async function (this: FestipodWorld) {\n expect(['profile', 'user-profile']).to.include(this.currentScreenId);\n // Verify profile content\n const hasProfileContent = this.hasText('profil') || this.hasText('Profil');\n expect(hasProfileContent, 'Profile page should display profile content').to.be.true;\n});",
|
||||
"lineNumber": 36
|
||||
"sourceCode": "Then('je peux voir mon profil', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('profile');\n const source = this.getRenderedText();\n // ProfileScreen.tsx has: <Avatar initials=\"MD\" size=\"lg\" />, <Title>Marie Dupont</Title>, @mariedupont\n expect(/<Avatar[^>]*initials=\"MD\"[^>]*size=\"lg\"/.test(source), 'Profile should have Avatar with initials=\"MD\" and size=\"lg\"').to.be.true;\n expect(/<Title[^>]*>Marie Dupont<\\/Title>/.test(source), 'Profile should have Title \"Marie Dupont\"').to.be.true;\n expect(/@mariedupont/.test(source), 'Profile should have username @mariedupont').to.be.true;\n});",
|
||||
"lineNumber": 43
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir le profil de l'utilisateur",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir le profil de l\\'utilisateur', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const hasProfileContent = this.hasText('Profil') || this.hasText('@');\n expect(hasProfileContent, 'User profile should display profile information').to.be.true;\n});",
|
||||
"lineNumber": 43
|
||||
"sourceCode": "Then('je peux voir le profil de l\\'utilisateur', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const source = this.getRenderedText();\n // UserProfileScreen.tsx has: <Avatar initials=\"JD\" size=\"lg\" />, <Title>Jean Durand</Title>, @jeandurand\n expect(/<Avatar[^>]*initials=\"JD\"[^>]*size=\"lg\"/.test(source), 'User profile should have Avatar with initials=\"JD\" and size=\"lg\"').to.be.true;\n expect(/<Title[^>]*>Jean Durand<\\/Title>/.test(source), 'User profile should have Title \"Jean Durand\"').to.be.true;\n expect(/@jeandurand/.test(source), 'User profile should have username @jeandurand').to.be.true;\n});",
|
||||
"lineNumber": 52
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir la liste des événements",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir la liste des événements', async function (this: FestipodWorld) {\n expect(['events', 'home', 'profile']).to.include(this.currentScreenId);\n // Verify events list is shown\n const hasEvents = this.hasText('Événement') || this.hasText('événement') || this.hasText('inscrits');\n expect(hasEvents, 'Page should display events list').to.be.true;\n});",
|
||||
"lineNumber": 49
|
||||
"sourceCode": "Then('je peux voir la liste des événements', async function (this: FestipodWorld) {\n const source = this.getRenderedText();\n if (this.currentScreenId === 'home') {\n // HomeScreen.tsx has: \"Événements à venir\" text and EventCard components\n expect(/Événements à venir/.test(source), 'Home screen should have \"Événements à venir\" text').to.be.true;\n } else if (this.currentScreenId === 'events') {\n // EventsScreen.tsx has: EventCard components with event data\n expect(/<Card[^>]*onClick/.test(source), 'Events screen should have clickable Card components').to.be.true;\n } else {\n this.attach(`UNEXPECTED SCREEN: \"${this.currentScreenId}\" is not expected to show events list`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 61
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir le QR code",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir le QR code', async function (this: FestipodWorld) {\n expect(['profile', 'share-profile', 'meeting-points']).to.include(this.currentScreenId);\n // Check for QR code related content\n const hasQRContent = this.hasText('QR') || this.hasText('Partager') || this.hasText('partager');\n expect(hasQRContent, 'Page should have QR code or share functionality').to.be.true;\n});",
|
||||
"lineNumber": 56
|
||||
"sourceCode": "Then('je peux voir le QR code', async function (this: FestipodWorld) {\n const source = this.getRenderedText();\n if (this.currentScreenId === 'share-profile') {\n // ShareProfileScreen.tsx has: \"QR Code\" comment and \"Scannez pour me retrouver\" text\n expect(/QR Code/.test(source), 'Share profile should have \"QR Code\" text').to.be.true;\n expect(/Scannez pour me retrouver/.test(source), 'Share profile should have \"Scannez pour me retrouver\" text').to.be.true;\n } else if (this.currentScreenId === 'meeting-points') {\n // MeetingPointsScreen.tsx has: \"Mon QR Code\" text and \"Scannez pour m'ajouter\"\n expect(/Mon QR Code/.test(source), 'Meeting points should have \"Mon QR Code\" text').to.be.true;\n expect(/Scannez pour m'ajouter/.test(source), 'Meeting points should have \"Scannez pour m\\'ajouter\" text').to.be.true;\n } else {\n // QR code is NOT on this screen\n this.attach(`NOT ON THIS SCREEN: QR code is on share-profile or meeting-points, not \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 75
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir le lien de partage",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir le lien de partage', async function (this: FestipodWorld) {\n expect(['profile', 'share-profile']).to.include(this.currentScreenId);\n const hasShareLink = this.hasText('Partager') || this.hasText('partager') || this.hasText('lien');\n expect(hasShareLink, 'Page should display share link functionality').to.be.true;\n});",
|
||||
"lineNumber": 63
|
||||
"sourceCode": "Then('je peux voir le lien de partage', async function (this: FestipodWorld) {\n const source = this.getRenderedText();\n if (this.currentScreenId === 'share-profile') {\n // ShareProfileScreen.tsx has: \"Mon lien de profil\" text and profileLink variable\n expect(/Mon lien de profil/.test(source), 'Share profile should have \"Mon lien de profil\" text').to.be.true;\n expect(/festipod\\.app\\/u\\//.test(source), 'Share profile should have profile link URL').to.be.true;\n } else {\n // Share link is NOT on this screen\n this.attach(`NOT ON THIS SCREEN: Share link is on share-profile, not \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 92
|
||||
},
|
||||
{
|
||||
"pattern": "un événement existe avec les données:",
|
||||
"keyword": "Given",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Given('un événement existe avec les données:', async function (this: FestipodWorld, dataTable) {\n const eventData = dataTable.rowsHash();\n this.attach(`Event data: ${JSON.stringify(eventData)}`, 'text/plain');\n});",
|
||||
"lineNumber": 69
|
||||
"sourceCode": "Given('un événement existe avec les données:', async function (this: FestipodWorld, dataTable) {\n // Cannot set up test data without backend/database\n const eventData = dataTable.rowsHash();\n this.attach(`CANNOT TEST: Setting up event data requires backend: ${JSON.stringify(eventData)}`, 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 105
|
||||
},
|
||||
{
|
||||
"pattern": "un utilisateur existe avec les données:",
|
||||
"keyword": "Given",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Given('un utilisateur existe avec les données:', async function (this: FestipodWorld, dataTable) {\n const userData = dataTable.rowsHash();\n this.attach(`User data: ${JSON.stringify(userData)}`, 'text/plain');\n});",
|
||||
"lineNumber": 74
|
||||
"sourceCode": "Given('un utilisateur existe avec les données:', async function (this: FestipodWorld, dataTable) {\n // Cannot set up test data without backend/database\n const userData = dataTable.rowsHash();\n this.attach(`CANNOT TEST: Setting up user data requires backend: ${JSON.stringify(userData)}`, 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 112
|
||||
},
|
||||
{
|
||||
"pattern": "je visualise l'événement {string}",
|
||||
"keyword": "Given",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Given('je visualise l\\'événement {string}', async function (this: FestipodWorld, eventName: string) {\n this.navigateTo('#/demo/event-detail');\n expect(this.currentScreen, 'Event detail screen should be loaded').to.not.be.null;\n this.attach(`Viewing event: ${eventName}`, 'text/plain');\n});",
|
||||
"lineNumber": 79
|
||||
"lineNumber": 119
|
||||
},
|
||||
{
|
||||
"pattern": "je visualise le profil de {string}",
|
||||
"keyword": "Given",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Given('je visualise le profil de {string}', async function (this: FestipodWorld, userName: string) {\n this.navigateTo('#/demo/user-profile');\n expect(this.currentScreen, 'User profile screen should be loaded').to.not.be.null;\n this.attach(`Viewing profile: ${userName}`, 'text/plain');\n});",
|
||||
"lineNumber": 85
|
||||
"lineNumber": 125
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran affiche les informations de l'événement",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('l\\'écran affiche les informations de l\\'événement', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n // Verify actual content is rendered\n const expectedContent = screenExpectedContent['event-detail'] || [];\n const renderedText = this.getRenderedText();\n\n let foundCount = 0;\n for (const content of expectedContent) {\n if (renderedText.includes(content)) {\n foundCount++;\n }\n }\n\n expect(foundCount, `At least one expected content item should be present`).to.be.greaterThan(0);\n});",
|
||||
"lineNumber": 91
|
||||
"sourceCode": "Then('l\\'écran affiche les informations de l\\'événement', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n const source = this.getRenderedText();\n // EventDetailScreen.tsx has: <Title>, 📅, 🕓, 📍 emojis, and \"À propos\" section\n expect(/<Title[^>]*>[^<]+<\\/Title>/.test(source), 'Event detail should have a Title').to.be.true;\n expect(/📅/.test(source), 'Event detail should have date emoji 📅').to.be.true;\n expect(/🕓/.test(source), 'Event detail should have time emoji 🕓').to.be.true;\n expect(/📍/.test(source), 'Event detail should have location emoji 📍').to.be.true;\n expect(/À propos/.test(source), 'Event detail should have \"À propos\" section').to.be.true;\n});",
|
||||
"lineNumber": 131
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran affiche les informations du profil",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('l\\'écran affiche les informations du profil', async function (this: FestipodWorld) {\n expect(['profile', 'user-profile']).to.include(this.currentScreenId);\n // Verify profile info is rendered\n const hasProfileInfo = this.hasText('Profil') || this.hasText('@') || this.hasText('Événement');\n expect(hasProfileInfo, 'Profile information should be displayed').to.be.true;\n});",
|
||||
"lineNumber": 107
|
||||
"sourceCode": "Then('l\\'écran affiche les informations du profil', async function (this: FestipodWorld) {\n const source = this.getRenderedText();\n if (this.currentScreenId === 'profile') {\n // ProfileScreen.tsx has: <Avatar initials=\"MD\" size=\"lg\" />, <Title>Marie Dupont</Title>, @mariedupont\n expect(/<Avatar[^>]*initials=\"MD\"/.test(source), 'Profile should have Avatar with initials=\"MD\"').to.be.true;\n expect(/<Title[^>]*>Marie Dupont<\\/Title>/.test(source), 'Profile should have Title \"Marie Dupont\"').to.be.true;\n expect(/@mariedupont/.test(source), 'Profile should have username @mariedupont').to.be.true;\n } else if (this.currentScreenId === 'user-profile') {\n // UserProfileScreen.tsx has: <Avatar initials=\"JD\" size=\"lg\" />, <Title>Jean Durand</Title>, @jeandurand\n expect(/<Avatar[^>]*initials=\"JD\"/.test(source), 'User profile should have Avatar with initials=\"JD\"').to.be.true;\n expect(/<Title[^>]*>Jean Durand<\\/Title>/.test(source), 'User profile should have Title \"Jean Durand\"').to.be.true;\n expect(/@jeandurand/.test(source), 'User profile should have username @jeandurand').to.be.true;\n } else {\n expect.fail(`Unexpected screen \"${this.currentScreenId}\" for profile info check`);\n }\n});",
|
||||
"lineNumber": 142
|
||||
},
|
||||
{
|
||||
"pattern": "je peux ajouter un commentaire",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux ajouter un commentaire', async function (this: FestipodWorld) {\n // Check for comment feature using precise detector\n const hasCommentFeature = this.hasField('Commentaire');\n\n if (!hasCommentFeature) {\n this.attach(`MISSING FEATURE: Comment functionality is not implemented in screen \"${this.currentScreenId}\"`, 'text/plain');\n this.attach(`Expected: textarea element or \"commentaire\" text in the screen`, 'text/plain');\n return 'pending'; // Mark as pending instead of failing\n }\n});",
|
||||
"lineNumber": 114
|
||||
"sourceCode": "Then('je peux ajouter un commentaire', async function (this: FestipodWorld) {\n // EventDetailScreen.tsx does NOT have comment functionality (no textarea, no \"commentaire\" text)\n // This feature is NOT implemented in the UI\n this.attach('NOT IMPLEMENTED: Comment functionality not in EventDetailScreen.tsx', 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 159
|
||||
},
|
||||
{
|
||||
"pattern": "je peux ajouter une note",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux ajouter une note', async function (this: FestipodWorld) {\n // Check for note feature - similar to comment\n const hasNoteFeature = this.hasText('Note') || this.hasText('note') || this.hasElement('textarea');\n\n if (!hasNoteFeature) {\n this.attach(`MISSING FEATURE: Note functionality is not implemented in screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 125
|
||||
"sourceCode": "Then('je peux ajouter une note', async function (this: FestipodWorld) {\n // No screen has note functionality implemented\n // This feature is NOT implemented in the UI\n this.attach('NOT IMPLEMENTED: Note functionality not implemented in any screen', 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 166
|
||||
},
|
||||
{
|
||||
"pattern": "je peux filtrer les événements par période",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux filtrer les événements par période', async function (this: FestipodWorld) {\n // Check for period filter feature\n const hasPeriodFilter = this.hasText('mois') || this.hasText('trimestre') || this.hasText('année') ||\n this.hasText('période') || this.hasText('Période');\n\n if (!hasPeriodFilter) {\n this.attach(`MISSING FEATURE: Period filter is not implemented in screen \"${this.currentScreenId}\"`, 'text/plain');\n return 'pending';\n }\n});",
|
||||
"lineNumber": 135
|
||||
"sourceCode": "Then('je peux filtrer les événements par période', async function (this: FestipodWorld) {\n // EventsScreen.tsx has filter badges (Tous, Cette semaine, Proches, Amis) but NOT period filter (mois/trimestre/année)\n // This feature is NOT implemented in the UI\n this.attach('NOT IMPLEMENTED: Period filter (mois/trimestre/année) not in EventsScreen.tsx', 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 173
|
||||
},
|
||||
{
|
||||
"pattern": "je peux modifier un commentaire",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux modifier un commentaire', async function (this: FestipodWorld) {\n // Comment editing is typically available where adding is\n const hasEditFeature = this.hasText('Modifier') || this.hasText('modifier') || this.hasElement('button');\n expect(hasEditFeature, 'Edit functionality should be available').to.be.true;\n});",
|
||||
"lineNumber": 146
|
||||
"sourceCode": "Then('je peux modifier un commentaire', async function (this: FestipodWorld) {\n // No comment edit functionality exists in any screen\n // This feature is NOT implemented in the UI\n this.attach('NOT IMPLEMENTED: Comment edit functionality not implemented', 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 180
|
||||
},
|
||||
{
|
||||
"pattern": "je peux supprimer un commentaire",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux supprimer un commentaire', async function (this: FestipodWorld) {\n // Delete is typically available where edit is\n const hasDeleteFeature = this.hasText('Supprimer') || this.hasText('supprimer') || this.hasElement('button');\n expect(hasDeleteFeature, 'Delete functionality should be available').to.be.true;\n});",
|
||||
"lineNumber": 152
|
||||
"sourceCode": "Then('je peux supprimer un commentaire', async function (this: FestipodWorld) {\n // No comment delete functionality exists in any screen\n // This feature is NOT implemented in the UI\n this.attach('NOT IMPLEMENTED: Comment delete functionality not implemented', 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 187
|
||||
},
|
||||
{
|
||||
"pattern": "je peux m'inscrire à l'événement",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux m\\'inscrire à l\\'événement', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n // Check for registration button\n const hasRegisterFeature = this.hasText('inscription') || this.hasText('Participer') ||\n this.hasText('participer') || this.hasText('S\\'inscrire') ||\n this.hasText('Rejoindre');\n expect(hasRegisterFeature, 'Registration feature should be available').to.be.true;\n});",
|
||||
"lineNumber": 158
|
||||
"sourceCode": "Then('je peux m\\'inscrire à l\\'événement', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n const source = this.getRenderedText();\n // EventDetailScreen.tsx line 49: {isJoined ? '✓ Inscrit' : 'Participer'}\n // The button shows \"Participer\" when not joined\n const hasParticiperButton = /isJoined \\? '✓ Inscrit' : 'Participer'/.test(source);\n expect(hasParticiperButton, 'Event detail should have Participer/Inscrit toggle button').to.be.true;\n});",
|
||||
"lineNumber": 194
|
||||
},
|
||||
{
|
||||
"pattern": "je peux me désinscrire de l'événement",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux me désinscrire de l\\'événement', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n // Unregister is typically on the same page as register\n const hasUnregisterFeature = this.hasText('désinscri') || this.hasText('Annuler') ||\n this.hasText('Quitter') || this.hasElement('button');\n expect(hasUnregisterFeature, 'Unregister feature should be available').to.be.true;\n});",
|
||||
"lineNumber": 167
|
||||
"sourceCode": "Then('je peux me désinscrire de l\\'événement', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('event-detail');\n const source = this.getRenderedText();\n // EventDetailScreen.tsx line 49: {isJoined ? '✓ Inscrit' : 'Participer'}\n // Same button toggles - clicking \"✓ Inscrit\" will unregister\n const hasInscritButton = /isJoined \\? '✓ Inscrit' : 'Participer'/.test(source);\n expect(hasInscritButton, 'Event detail should have Participer/Inscrit toggle button (click to unregister)').to.be.true;\n});",
|
||||
"lineNumber": 203
|
||||
},
|
||||
{
|
||||
"pattern": "je peux contacter l'utilisateur",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux contacter l\\'utilisateur', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n // Check for contact functionality\n const hasContactFeature = this.hasText('Contact') || this.hasText('Message') ||\n this.hasText('message') || this.hasElement('button');\n expect(hasContactFeature, 'Contact feature should be available').to.be.true;\n});",
|
||||
"lineNumber": 175
|
||||
"sourceCode": "Then('je peux contacter l\\'utilisateur', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const source = this.getRenderedText();\n // UserProfileScreen.tsx line 44: <Button>Contacter</Button>\n const hasContactButton = /<Button>Contacter<\\/Button>/.test(source);\n expect(hasContactButton, 'User profile should have \"Contacter\" button').to.be.true;\n});",
|
||||
"lineNumber": 212
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir les événements auxquels l'utilisateur a participé",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux voir les événements auxquels l\\'utilisateur a participé', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n // Check for user's events\n const hasUserEvents = this.hasText('Événement') || this.hasText('événement') ||\n this.hasText('Participation') || this.hasText('participation');\n expect(hasUserEvents, 'User events should be visible').to.be.true;\n});",
|
||||
"lineNumber": 183
|
||||
"sourceCode": "Then('je peux voir les événements auxquels l\\'utilisateur a participé', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const source = this.getRenderedText();\n // UserProfileScreen.tsx line 52: \"Événements en commun\" section with pastEvents\n expect(/Événements en commun/.test(source), 'User profile should have \"Événements en commun\" section').to.be.true;\n expect(/pastEvents/.test(source), 'User profile should display pastEvents data').to.be.true;\n});",
|
||||
"lineNumber": 220
|
||||
},
|
||||
{
|
||||
"pattern": "je peux configurer mes notifications",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux configurer mes notifications', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('settings');\n // Check for notification settings\n const hasNotificationSetting = this.hasText('Notification') || this.hasText('notification');\n expect(hasNotificationSetting, 'Notification settings should be visible').to.be.true;\n});",
|
||||
"lineNumber": 191
|
||||
"sourceCode": "Then('je peux configurer mes notifications', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('settings');\n const source = this.getRenderedText();\n // SettingsScreen.tsx line 25: <Text>Notifications</Text> with Toggle\n expect(/>Notifications</.test(source), 'Settings should have \"Notifications\" text').to.be.true;\n expect(/<Toggle[^>]*checked=\\{notifications\\}/.test(source), 'Settings should have Toggle for notifications').to.be.true;\n});",
|
||||
"lineNumber": 228
|
||||
},
|
||||
{
|
||||
"pattern": "je peux définir mon rayon de notification",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux définir mon rayon de notification', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('settings');\n // Check for location/radius setting\n const hasRadiusSetting = this.hasText('Localisation') || this.hasText('localisation') ||\n this.hasText('rayon') || this.hasText('Rayon');\n expect(hasRadiusSetting, 'Location/radius setting should be visible').to.be.true;\n});",
|
||||
"lineNumber": 198
|
||||
"sourceCode": "Then('je peux définir mon rayon de notification', async function (this: FestipodWorld) {\n // SettingsScreen.tsx has \"Localisation\" toggle but NOT \"rayon\" or \"km\" setting\n // This feature is NOT implemented in the UI\n this.attach('NOT IMPLEMENTED: Radius setting (rayon/km) is not in SettingsScreen.tsx', 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 236
|
||||
},
|
||||
{
|
||||
"pattern": "je peux définir mes thématiques d'intérêt",
|
||||
"keyword": "Then",
|
||||
"file": "screen.steps.ts",
|
||||
"sourceCode": "Then('je peux définir mes thématiques d\\'intérêt', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('settings');\n // Settings page should allow configuring interests (or it could be on profile)\n // For now just verify we're on settings\n expect(this.currentScreen, 'Settings screen should be loaded').to.not.be.null;\n});",
|
||||
"lineNumber": 206
|
||||
"sourceCode": "Then('je peux définir mes thématiques d\\'intérêt', async function (this: FestipodWorld) {\n // SettingsScreen.tsx does NOT have thematic/interest settings\n // This feature is NOT implemented in the UI\n this.attach('NOT IMPLEMENTED: Thematic/interest settings not in SettingsScreen.tsx', 'text/plain');\n return 'pending';\n});",
|
||||
"lineNumber": 243
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
+141
-141
@@ -16,10 +16,10 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
{
|
||||
"featureId": "us-13",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 4,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.018Z",
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T18:01:45.485Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la création d'événement",
|
||||
@@ -31,14 +31,14 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Remplir le formulaire de création d'événement",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier la présence du bouton de création",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier la présence du bouton d'annulation",
|
||||
"name": "Pouvoir annuler la création d'événement",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
@@ -46,10 +46,10 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
{
|
||||
"featureId": "us-3",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"passed": 3,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.018Z",
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T18:01:45.485Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux détails d'un événement terminé",
|
||||
@@ -65,21 +65,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données affichées",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-5",
|
||||
"totalScenarios": 5,
|
||||
"passed": 4,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T10:00:42.018Z",
|
||||
"skipped": 5,
|
||||
"lastRun": "2026-01-18T18:01:45.485Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Voir les commentaires existants",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Ajouter un commentaire",
|
||||
@@ -87,25 +87,25 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Modifier un commentaire",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Supprimer un commentaire",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'écran",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-7",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.018Z",
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-18T18:01:45.485Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Consulter un événement avant inscription",
|
||||
@@ -113,11 +113,11 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "S'inscrire à un événement",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Se désinscrire d'un événement",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Rechercher un événement existant",
|
||||
@@ -125,21 +125,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'écran",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-8",
|
||||
"totalScenarios": 4,
|
||||
"passed": 3,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T10:00:42.018Z",
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T18:01:45.485Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Consulter un macro-événement",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les événements rattachés",
|
||||
@@ -147,21 +147,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Rattacher un événement existant",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir la consolidation des participants",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-16",
|
||||
"totalScenarios": 6,
|
||||
"passed": 6,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.018Z",
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux points de rencontre",
|
||||
@@ -169,11 +169,11 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Créer un point de rencontre",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Définir le lieu de rencontre",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Définir l'heure de rencontre",
|
||||
@@ -181,11 +181,11 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Échanger des liens de contact",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données requises",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -195,7 +195,7 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 5,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Partager un événement auquel je participe",
|
||||
@@ -222,10 +222,10 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
{
|
||||
"featureId": "us-18",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 1,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Configurer les notifications de nouveaux participants",
|
||||
@@ -233,19 +233,19 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Activer les notifications pour un événement",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Filtrer les notifications par réseau",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les nouveaux participants sur l'accueil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données des paramètres",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -255,7 +255,7 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
"passed": 1,
|
||||
"failed": 0,
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Voir les événements à venir sur l'accueil",
|
||||
@@ -282,10 +282,10 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
{
|
||||
"featureId": "us-10",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 4,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au profil d'un participant",
|
||||
@@ -301,7 +301,7 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les informations du profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les détails du profil utilisateur",
|
||||
@@ -312,14 +312,14 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
{
|
||||
"featureId": "us-12",
|
||||
"totalScenarios": 6,
|
||||
"passed": 6,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la liste des événements depuis le profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à la liste des événements depuis découvrir",
|
||||
@@ -327,7 +327,7 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Filtrer par date",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Filtrer par personne",
|
||||
@@ -335,21 +335,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'écran événements",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'écran profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-15",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"passed": 3,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la liste des inscrits",
|
||||
@@ -365,17 +365,17 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'écran",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-20",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 3,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 2,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à mon profil",
|
||||
@@ -383,7 +383,7 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Voir mon réseau",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir un profil de mon réseau",
|
||||
@@ -395,17 +395,17 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données du profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-21",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux paramètres de profil",
|
||||
@@ -417,55 +417,55 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Rendre le profil public",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données des paramètres",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données du profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-22",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 5,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au partage de profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir le QR code de parrainage",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir le lien de parrainage",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les statistiques de parrainage",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données du profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-23",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au partage depuis le profil",
|
||||
@@ -473,11 +473,11 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Voir le QR code",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir le lien de partage",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à l'écran de partage dédié",
|
||||
@@ -485,17 +485,17 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données du profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-24",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 2,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux paramètres de notification",
|
||||
@@ -507,21 +507,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Voir les activités de mes contacts sur l'accueil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données des paramètres",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-25",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"passed": 1,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux paramètres de notification",
|
||||
@@ -529,33 +529,33 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Configurer le rayon de notification",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Configurer les thématiques d'intérêt",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données des paramètres",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-26",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 2,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la création d'événement",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Définir le rayon d'intérêt",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Choisir une thématique",
|
||||
@@ -570,14 +570,14 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
{
|
||||
"featureId": "us-9",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 2,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au profil pour voir la photo",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Naviguer vers le profil depuis la liste des participants",
|
||||
@@ -589,21 +589,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les champs de données du profil",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-1",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"passed": 1,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux détails d'un événement terminé",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Consulter la liste des participants d'un atelier",
|
||||
@@ -611,29 +611,29 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Consulter les ressources d'un atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données affichées pour un atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-11",
|
||||
"totalScenarios": 4,
|
||||
"passed": 3,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au bilan consolidé",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les commentaires regroupés par atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir la synthèse globale",
|
||||
@@ -641,21 +641,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données du bilan",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-14",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"passed": 1,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la création d'atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les champs obligatoires pour créer un atelier",
|
||||
@@ -663,33 +663,33 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Créer un atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Modifier un atelier existant",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Supprimer un atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-2",
|
||||
"totalScenarios": 4,
|
||||
"passed": 3,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la zone de notes personnelles",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à la zone de partage publique",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Ajouter une note personnelle",
|
||||
@@ -697,21 +697,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Ajouter un lien/ressource",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-4",
|
||||
"totalScenarios": 4,
|
||||
"passed": 3,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Voir les commentaires existants d'un atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Ajouter un commentaire à un atelier",
|
||||
@@ -719,21 +719,21 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "Modifier un commentaire existant",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Supprimer un commentaire",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-6",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-18T10:00:42.019Z",
|
||||
"skipped": 2,
|
||||
"lastRun": "2026-01-18T18:01:45.486Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Rechercher un événement public existant",
|
||||
@@ -745,11 +745,11 @@ const rawResults: RawFeatureTestStatus[] = [
|
||||
},
|
||||
{
|
||||
"name": "S'inscrire à un atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Se désinscrire d'un atelier",
|
||||
"status": "passed"
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user