NextGraph integration (WIP), broker banner, and feature-based architecture
- Add NextGraph data layer with @ng-org/orm, SHEX shapes (Event, UserProfile, Participation), session management, and FestipodDataContext with dual-mode operation (connected via NextGraph or local seed data) - Add BrokerBanner and NgStatus components showing connection status - Refactor to feature-based architecture: organize code by business domain (event, user, home, auth, workshop, meeting, notification) instead of technical layer. Modules only import from shared/, never from each other - Collocate BDD features and step definitions with their modules: event-specific steps in event/steps/, user steps in user/steps/, shared generic steps remain in shared/steps/ - Set up multi-layer BDD structure (frontend/backend/e2e steps per module) - Add project documentation (AGENTS.md, .project/knowledge/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Centralized data architecture for Festipod mockups
|
||||
*
|
||||
* This module provides:
|
||||
* - User stories definitions
|
||||
* - Screen-to-story bidirectional links (computed automatically from feature files)
|
||||
* - Sample data for mockups
|
||||
*
|
||||
* screenIds are extracted automatically from .feature files by parse-features.ts.
|
||||
* Run `bun run features:parse` to update them.
|
||||
*/
|
||||
|
||||
import { parsedFeatures } from './features';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export type StoryCategory = 'WORKSHOP' | 'EVENT' | 'USER' | 'MEETING' | 'NOTIF';
|
||||
|
||||
export interface UserStoryDefinition {
|
||||
id: string;
|
||||
priority: number;
|
||||
category: StoryCategory;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface UserStory extends UserStoryDefinition {
|
||||
screenIds: string[];
|
||||
}
|
||||
|
||||
export interface ScreenStories {
|
||||
screenId: string;
|
||||
stories: UserStory[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Category metadata
|
||||
// ============================================================================
|
||||
|
||||
export const categoryLabels: Record<StoryCategory, string> = {
|
||||
WORKSHOP: 'Atelier',
|
||||
EVENT: 'Événement',
|
||||
USER: 'Utilisateur',
|
||||
MEETING: 'Point de rencontre',
|
||||
NOTIF: 'Notification',
|
||||
};
|
||||
|
||||
export const categoryColors: Record<StoryCategory, string> = {
|
||||
WORKSHOP: '#9c27b0',
|
||||
EVENT: '#2196f3',
|
||||
USER: '#4caf50',
|
||||
MEETING: '#ff9800',
|
||||
NOTIF: '#f44336',
|
||||
};
|
||||
|
||||
export const priorityLabels: Record<number, string> = {
|
||||
0: 'Impossible',
|
||||
1: 'Haute',
|
||||
2: 'Moyenne',
|
||||
3: 'Basse',
|
||||
};
|
||||
|
||||
export const priorityColors: Record<number, string> = {
|
||||
0: '#999',
|
||||
1: '#c00',
|
||||
2: '#e60',
|
||||
3: '#08a',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// User Stories Data
|
||||
// ============================================================================
|
||||
|
||||
const userStoriesData: UserStoryDefinition[] = [
|
||||
// Row 1 - WORKSHOP - Priority 3
|
||||
{
|
||||
id: 'us-1',
|
||||
priority: 3,
|
||||
category: 'WORKSHOP',
|
||||
title: 'Visualiser un événement terminé',
|
||||
description: 'En tant qu\'utilisateur, je peux visualiser un événement terminé et consulter le programme détaillé des ateliers par journée/heure afin de voir les personnes qui ont participé à chaque atelier et consulter les notes/liens (Ressources)/commentaires de cet atelier (Zone de partage Collective).',
|
||||
},
|
||||
// Row 2 - WORKSHOP - Priority 3
|
||||
{
|
||||
id: 'us-2',
|
||||
priority: 3,
|
||||
category: 'WORKSHOP',
|
||||
title: 'Visualiser un événement terminé (notes)',
|
||||
description: 'En tant qu\'utilisateur, je peux visualiser un événement terminé et consulter le programme détaillé des ateliers par journée/heure afin de ajouter d\'éventuelles prises de notes/liens (ressources) ou des commentaires associés à l\'atelier (Zone privée de l\'utilisateur et/ou Zone partage publique).',
|
||||
},
|
||||
// Row 3 - EVENT - Priority 1
|
||||
{
|
||||
id: 'us-3',
|
||||
priority: 1,
|
||||
category: 'EVENT',
|
||||
title: 'Visualiser un événement terminé',
|
||||
description: 'En tant qu\'utilisateur, je peux visualiser un événement terminé et consulter la description de l\'événement afin de voir les personnes qui ont participé à cet événement.',
|
||||
},
|
||||
// Row 4 - WORKSHOP - Priority 3
|
||||
{
|
||||
id: 'us-4',
|
||||
priority: 3,
|
||||
category: 'WORKSHOP',
|
||||
title: 'Ajouter/modifier/supprimer un commentaire à un atelier',
|
||||
description: 'En tant qu\'utilisateur, je peux consulter et ajouter/modifier/supprimer un commentaire à un atelier en sélectionnant l\'icône « ajouter un commentaire » en dessous du titre de l\'atelier afin de voir les commentaires précédents et ajouter mes commentaires, mentionner mes ressentis, faire part de mes annotations.',
|
||||
},
|
||||
// Row 5 - EVENT - Priority 3
|
||||
{
|
||||
id: 'us-5',
|
||||
priority: 3,
|
||||
category: 'EVENT',
|
||||
title: 'Ajouter/modifier/supprimer un commentaire à un événement',
|
||||
description: 'En tant qu\'utilisateur, je peux consulter et ajouter/modifier/supprimer un commentaire à un événement en sélectionnant l\'icône « ajouter un commentaire » en dessous du titre de l\'événement (Notes Privées / Personnelles) indiquant les interactions avec les individus rencontrés (Date / Heure / Lieu) afin de voir les commentaires précédents et ajouter mes commentaires, mentionner mes ressentis, faire part de mes annotations.',
|
||||
},
|
||||
// Row 6 - WORKSHOP - Priority 3
|
||||
{
|
||||
id: 'us-6',
|
||||
priority: 3,
|
||||
category: 'WORKSHOP',
|
||||
title: 'M\'inscrire/me désinscrire à un événement (atelier)',
|
||||
description: 'En tant qu\'utilisateur, je peux m\'inscrire/me désinscrire à un événement en : 1- regardant si l\'événement public existe déjà, 2- M\'enregistrant sur les différents ateliers afin de m\'inscrire à l\'atelier tout en visualisant les personnes qui sont déjà pré-inscrites.',
|
||||
},
|
||||
// Row 7 - EVENT - Priority 1
|
||||
{
|
||||
id: 'us-7',
|
||||
priority: 1,
|
||||
category: 'EVENT',
|
||||
title: 'M\'inscrire/me désinscrire à un événement',
|
||||
description: 'En tant qu\'utilisateur, je peux m\'inscrire/me désinscrire à un événement après avoir consulté la description de l\'événement, les dates et le lieu de tenue de l\'événement s\'il existe déjà dans le système, ou en le retrouvant dans une base existante (Mobilizon, etc.).',
|
||||
},
|
||||
// Row 8 - EVENT - Priority 3
|
||||
{
|
||||
id: 'us-8',
|
||||
priority: 3,
|
||||
category: 'EVENT',
|
||||
title: 'Consulter et m\'inscrire à un macro-événement',
|
||||
description: 'En tant qu\'utilisateur, je peux consulter et m\'inscrire à un événement de type « Macro-événement » en créant ou en rattachant des événements existants à ce macro-événement afin de rattacher des événements existants à une thématique particulière ou créer un événement qui est répété est plusieurs périodes dans l\'année (les résidences reconnecté) et voir une consolidation des commentaires / Liens/Ressources /participants de chaque événement rattaché.',
|
||||
},
|
||||
// Row 9 - USER - Priority 0
|
||||
{
|
||||
id: 'us-9',
|
||||
priority: 0,
|
||||
category: 'USER',
|
||||
title: 'Visualiser la photo d\'un individu',
|
||||
description: 'En tant qu\'utilisateur, je peux visualiser la photo d\'un individu (ou ajouter une photo personnelle sur une fiche existante) et consulter la liste des inscrits à un atelier afin de identifier les personnes que j\'ai rencontré dont je n\'ai pas noté leur nom.',
|
||||
},
|
||||
// Row 10 - USER - Priority 1
|
||||
{
|
||||
id: 'us-10',
|
||||
priority: 1,
|
||||
category: 'USER',
|
||||
title: 'Visualiser la fiche/le profil d\'un participant',
|
||||
description: 'En tant qu\'utilisateur, je peux sélectionner un individu dans la liste des inscrits à un événement/atelier afin de voir les événements auxquels la personne a participé et voir un formulaire de contact pour intéragir avec elle.',
|
||||
},
|
||||
// Row 11 - WORKSHOP - Priority 3
|
||||
{
|
||||
id: 'us-11',
|
||||
priority: 3,
|
||||
category: 'WORKSHOP',
|
||||
title: 'Visualiser le bilan consolidé de l\'événement',
|
||||
description: 'En tant qu\'utilisateur, je peux visualiser le bilan consolidé de l\'événement en consultant l\'ensemble des commentaires regroupés par atelier afin de obtenir une synthèse du contenu de chaque atelier et de l\'ensemble des ateliers constituant l\'événement.',
|
||||
},
|
||||
// Row 12 - USER - Priority 2
|
||||
{
|
||||
id: 'us-12',
|
||||
priority: 2,
|
||||
category: 'USER',
|
||||
title: 'Consulter la carte / tableau des événements',
|
||||
description: 'En tant qu\'utilisateur, je peux consulter la carte / tableau des événements auxquels j\'ai participé en filtrant les événements auxquels j\'ai participé par dates ou par personne afin de avoir une vue consolidée des événements auxquels j\'ai participé ainsi que le lieu ou j\'ai pu rencontrer une personne ou une vue consolidée des événements auxquels une personne a participé.',
|
||||
},
|
||||
// Row 13 - EVENT - Priority 1
|
||||
{
|
||||
id: 'us-13',
|
||||
priority: 1,
|
||||
category: 'EVENT',
|
||||
title: 'Créer/Modifier/Supprimer un événement',
|
||||
description: 'En tant qu\'utilisateur, je peux créer/modifier/supprimer un événement en choisissant les dates et les horaires de début et de fin de l\'événement, retirer une organisation (personne ou structure) et choisir un lieu. [Données obligatoires : Titre, description, image, adresse, + thématique (sera utilisé pour les notifications)] afin de créer/présenter le contenu de cet événement et le catégoriser par type/thématique.',
|
||||
},
|
||||
// Row 14 - WORKSHOP - Priority 3
|
||||
{
|
||||
id: 'us-14',
|
||||
priority: 3,
|
||||
category: 'WORKSHOP',
|
||||
title: 'Créer/Modifier/Supprimer un atelier',
|
||||
description: 'En tant qu\'utilisateur, je peux créer/modifier/supprimer un atelier en sélectionnant mon événement et en saisissant les dates et les horaires de début et de fin de l\'atelier afin de définir le programme de mon événement et ajouter description de cet atelier.',
|
||||
},
|
||||
// Row 15 - USER - Priority 1
|
||||
{
|
||||
id: 'us-15',
|
||||
priority: 1,
|
||||
category: 'USER',
|
||||
title: 'Visualiser les inscrits à un atelier/événement',
|
||||
description: 'En tant qu\'utilisateur, je peux visualiser les inscrits à un atelier/événement en sélectionnant l\'atelier/l\'événement désiré dans la liste des événements/ateliers de l\'événement afin de consulter la liste des inscrits triée par ordre alphabétique.',
|
||||
},
|
||||
// Row 16 - MEETING - Priority 1
|
||||
{
|
||||
id: 'us-16',
|
||||
priority: 1,
|
||||
category: 'MEETING',
|
||||
title: 'Indiquer un ou plusieurs points de rencontre',
|
||||
description: 'En tant qu\'utilisateur, je peux indiquer un ou plusieurs points de rencontre en précisant le lieu (café,...) ainsi que l\'heure de cette rencontre (ou le délai ex : 30min avant l\'événement) afin de croiser et faire connaissance d\'autres participants. Je peux aussi échanger avec les autres participants nos liens de contact (QR code ou lien ou bluetooth ?).',
|
||||
},
|
||||
// Row 17 - NOTIF - Priority 2
|
||||
{
|
||||
id: 'us-17',
|
||||
priority: 2,
|
||||
category: 'NOTIF',
|
||||
title: 'Informer automatiquement d\'autres utilisateurs',
|
||||
description: 'En tant qu\'utilisateur, je peux informer automatiquement d\'autres utilisateurs de ma participation à un événement en utilisant un système de notifications (e-mail,...) pour transmettre le lien de l\'événement afin d\'informer les utilisateurs qui résident à proximité de l\'événement (distance à déterminer/configurer). Ou bien informer les utilisateurs ayant manifesté un intérêt pour la thématique de l\'événement. Ou bien informer mes abonnés. Ou bien les trois à la fois.',
|
||||
},
|
||||
// Row 18 - NOTIF - Priority 2
|
||||
{
|
||||
id: 'us-18',
|
||||
priority: 2,
|
||||
category: 'NOTIF',
|
||||
title: 'Être informé lorsque de nouveaux participants s\'inscrivent',
|
||||
description: 'En tant qu\'utilisateur, je peux être informé lorsque de nouveaux participants s\'inscrivent à un événement auquel je suis inscrit en utilisant un système de notifications (e-mail,...) afin de savoir qui participe à un événement. Éventuellement être uniquement informé des participants que je connais déjà (paramétrable ex : mon réseau).',
|
||||
},
|
||||
// Row 19 - NOTIF - Priority 2
|
||||
{
|
||||
id: 'us-19',
|
||||
priority: 2,
|
||||
category: 'NOTIF',
|
||||
title: 'Recevoir un récapitulatif des prochaines rencontres',
|
||||
description: 'En tant qu\'utilisateur, je peux recevoir un récapitulatif des prochaines rencontres en réceptionnant une liste des événements auxquels je suis inscrit ou qui sont proches de chez moi afin de établir un programme des événements auxquels je participe par période (mois, trimestre, année,...).',
|
||||
},
|
||||
// Row 20 - USER - Priority 1
|
||||
{
|
||||
id: 'us-20',
|
||||
priority: 1,
|
||||
category: 'USER',
|
||||
title: 'Voir le profil des personnes faisant partie de mon réseau',
|
||||
description: 'En tant qu\'utilisateur, je peux voir le profil des personnes faisant partie de mon réseau ainsi que le profil des personnes publiques et consulter la description de l\'événement afin de savoir si j\'ai envie de participer à cet événement.',
|
||||
},
|
||||
// Row 21 - USER - Priority 2
|
||||
{
|
||||
id: 'us-21',
|
||||
priority: 2,
|
||||
category: 'USER',
|
||||
title: 'Décider que tous les utilisateurs puissent suivre mes activités',
|
||||
description: 'En tant qu\'utilisateur, je peux décider que tous les utilisateurs puissent suivre toutes mes activités en déclarant mon profil public afin de communiquer au sujet de mes déplacements et faire la publicité des événements auxquels je participe.',
|
||||
},
|
||||
// Row 22 - USER - Priority 2
|
||||
{
|
||||
id: 'us-22',
|
||||
priority: 2,
|
||||
category: 'USER',
|
||||
title: 'Parrainer un nouvel utilisateur',
|
||||
description: 'En tant qu\'utilisateur, je peux parrainer un nouvel utilisateur en lui partageant mon QR code ou lien de contact afin de savoir combien de personnes ont rejoint le réseau grâce à moi.',
|
||||
},
|
||||
// Row 23 - USER - Priority 1
|
||||
{
|
||||
id: 'us-23',
|
||||
priority: 1,
|
||||
category: 'USER',
|
||||
title: 'Me connecter avec d\'autres utilisateurs',
|
||||
description: 'En tant qu\'utilisateur, je peux me connecter avec d\'autres utilisateurs en partageant mon QR code ou mon lien de contact afin de étendre mon réseau.',
|
||||
},
|
||||
// Row 24 - USER - Priority 2
|
||||
{
|
||||
id: 'us-24',
|
||||
priority: 2,
|
||||
category: 'USER',
|
||||
title: 'Être notifié des activités de mes contacts',
|
||||
description: 'En tant qu\'utilisateur, je peux être notifié lorsqu\'un contact participe à des événements afin de obtenir une synthèse du contenu de chaque atelier et de l\'ensemble des ateliers constituant l\'événement.',
|
||||
},
|
||||
// Row 25 - USER - Priority 2
|
||||
{
|
||||
id: 'us-25',
|
||||
priority: 2,
|
||||
category: 'USER',
|
||||
title: 'Être averti des événements susceptibles de m\'intéresser',
|
||||
description: 'En tant qu\'utilisateur, je peux être notifié lorsqu\'un nouvel événement est ajouté près de chez moi et/ou avec une thématique qui m\'intéresse en configurant mes notifications.',
|
||||
},
|
||||
// Row 26 - USER - Priority 2
|
||||
{
|
||||
id: 'us-26',
|
||||
priority: 2,
|
||||
category: 'USER',
|
||||
title: 'Définir la portée d\'un événement',
|
||||
description: 'En tant qu\'utilisateur, je peux créer/présenter le contenu de cet événement et le catégoriser par type/thématique (Liste fixe à déterminer) en indiquant son rayon d\'intérêt en kilomètres afin de m\'assurer que les utilisateurs qui habitent trop loin ne reçoivent pas de notification.',
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Computed data & indexes
|
||||
// ============================================================================
|
||||
|
||||
// Build a map of feature id -> screenIds from parsed features
|
||||
const featureScreenIds = new Map<string, string[]>();
|
||||
for (const feature of parsedFeatures) {
|
||||
featureScreenIds.set(feature.id, feature.screenIds);
|
||||
}
|
||||
|
||||
// Export the stories with screenIds computed from parsed features
|
||||
export const userStories: UserStory[] = userStoriesData.map(story => ({
|
||||
...story,
|
||||
screenIds: featureScreenIds.get(story.id) || [],
|
||||
}));
|
||||
|
||||
// Build reverse index: screenId -> stories
|
||||
const storiesByScreenIndex = new Map<string, UserStory[]>();
|
||||
|
||||
userStories.forEach(story => {
|
||||
story.screenIds.forEach(screenId => {
|
||||
if (!storiesByScreenIndex.has(screenId)) {
|
||||
storiesByScreenIndex.set(screenId, []);
|
||||
}
|
||||
storiesByScreenIndex.get(screenId)!.push(story);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Query functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get all stories linked to a specific screen
|
||||
*/
|
||||
export function getStoriesForScreen(screenId: string): UserStory[] {
|
||||
return storiesByScreenIndex.get(screenId) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all stories filtered by priority
|
||||
*/
|
||||
export function getStoriesByPriority(priority: number): UserStory[] {
|
||||
return userStories.filter(story => story.priority === priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all stories filtered by category
|
||||
*/
|
||||
export function getStoriesByCategory(category: StoryCategory): UserStory[] {
|
||||
return userStories.filter(story => story.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a story by its ID
|
||||
*/
|
||||
export function getStoryById(id: string): UserStory | undefined {
|
||||
return userStories.find(story => story.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all screen IDs that have linked stories
|
||||
*/
|
||||
export function getScreenIdsWithStories(): string[] {
|
||||
return Array.from(storiesByScreenIndex.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get story count for a screen
|
||||
*/
|
||||
export function getStoryCountForScreen(screenId: string): number {
|
||||
return storiesByScreenIndex.get(screenId)?.length || 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sample data for mockups
|
||||
// ============================================================================
|
||||
|
||||
export const sampleUsers = [
|
||||
{ id: '1', name: 'Marie Dupont', initials: 'MD', username: '@mariedupont' },
|
||||
{ id: '2', name: 'Jean Durand', initials: 'JD', username: '@jeandurand' },
|
||||
{ id: '3', name: 'Alice Martin', initials: 'AM', username: '@alice' },
|
||||
{ id: '4', name: 'Baptiste Morel', initials: 'BM', username: '@baptiste' },
|
||||
{ id: '5', name: 'Camille Dubois', initials: 'CD', username: '@camille' },
|
||||
{ id: '6', name: 'David Leroy', initials: 'DL', username: '@david' },
|
||||
];
|
||||
|
||||
export const sampleEvents = [
|
||||
{ id: '1', title: 'Résidence Reconnexion', date: '16-20 fév.', location: 'Le Revel, Rogues (30)', participants: 24 },
|
||||
{ id: '2', title: 'Atelier low-tech', date: '8 fév.', location: 'La Maison du Vélo, Lyon', participants: 12 },
|
||||
{ id: '3', title: 'Forum Ouvert Transition', date: '22 fév.', location: 'Tiers-lieu L\'Hermitage', participants: 45 },
|
||||
{ id: '4', title: 'Formation CNV', date: '1 mars', location: 'MJC Montplaisir, Lyon', participants: 16 },
|
||||
];
|
||||
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* Seed/fake data for Festipod mockups.
|
||||
* Used as defaults when not connected to NextGraph or when the store is empty.
|
||||
*/
|
||||
|
||||
import type { FpEventData, FpUserData, FpParticipationData, FpMeetingPointData, FpFriendshipData } from './types';
|
||||
|
||||
// Current user (the logged-in user in the mockup)
|
||||
export const CURRENT_USER_ID = 'user-1';
|
||||
|
||||
export const seedUsers: FpUserData[] = [
|
||||
{
|
||||
id: 'user-1',
|
||||
name: 'Marie Dupont',
|
||||
initials: 'MD',
|
||||
username: '@mariedupont',
|
||||
bio: 'Passionnée de transition écologique et de rencontres humaines.',
|
||||
city: 'Lyon, France',
|
||||
eventsCount: 12,
|
||||
friendsCount: 48,
|
||||
participationsCount: 156,
|
||||
},
|
||||
{
|
||||
id: 'user-2',
|
||||
name: 'Jean Durand',
|
||||
initials: 'JD',
|
||||
username: '@jeandurand',
|
||||
eventsCount: 8,
|
||||
friendsCount: 23,
|
||||
participationsCount: 42,
|
||||
},
|
||||
{
|
||||
id: 'user-3',
|
||||
name: 'Alice Martin',
|
||||
initials: 'AM',
|
||||
username: '@alice',
|
||||
},
|
||||
{
|
||||
id: 'user-4',
|
||||
name: 'Baptiste Morel',
|
||||
initials: 'BM',
|
||||
username: '@baptiste',
|
||||
},
|
||||
{
|
||||
id: 'user-5',
|
||||
name: 'Camille Dubois',
|
||||
initials: 'CD',
|
||||
username: '@camille',
|
||||
},
|
||||
{
|
||||
id: 'user-6',
|
||||
name: 'David Leroy',
|
||||
initials: 'DL',
|
||||
username: '@david',
|
||||
},
|
||||
{
|
||||
id: 'user-7',
|
||||
name: 'Thomas Martin',
|
||||
initials: 'TM',
|
||||
username: '@thomas',
|
||||
},
|
||||
{
|
||||
id: 'user-8',
|
||||
name: 'Emma Bernard',
|
||||
initials: 'EB',
|
||||
username: '@emma',
|
||||
},
|
||||
{
|
||||
id: 'user-9',
|
||||
name: 'François Petit',
|
||||
initials: 'FP',
|
||||
username: '@francois',
|
||||
},
|
||||
{
|
||||
id: 'user-10',
|
||||
name: 'Emma Girard',
|
||||
initials: 'EG',
|
||||
username: '@emma',
|
||||
eventsCount: 7,
|
||||
},
|
||||
// Public profiles
|
||||
{
|
||||
id: 'user-pub-1',
|
||||
name: 'Léa Bernard',
|
||||
initials: 'LB',
|
||||
username: '@leabernard',
|
||||
role: 'Relayeuse',
|
||||
isPublic: true,
|
||||
eventsCount: 45,
|
||||
},
|
||||
{
|
||||
id: 'user-pub-2',
|
||||
name: 'Marc Richard',
|
||||
initials: 'MR',
|
||||
username: '@marcrichard',
|
||||
role: 'Animateur',
|
||||
isPublic: true,
|
||||
eventsCount: 67,
|
||||
},
|
||||
{
|
||||
id: 'user-pub-3',
|
||||
name: 'Sophie Fontaine',
|
||||
initials: 'SF',
|
||||
username: '@sophief',
|
||||
role: 'Créatrice',
|
||||
isPublic: true,
|
||||
eventsCount: 23,
|
||||
},
|
||||
{
|
||||
id: 'user-pub-4',
|
||||
name: 'Pierre Gagnon',
|
||||
initials: 'PG',
|
||||
username: '@pierreg',
|
||||
role: 'Relayeur',
|
||||
isPublic: true,
|
||||
eventsCount: 89,
|
||||
},
|
||||
];
|
||||
|
||||
export const seedEvents: FpEventData[] = [
|
||||
{
|
||||
id: 'event-1',
|
||||
title: 'Résidence Reconnexion',
|
||||
date: 'Lun. 16 - Ven. 20 fév.',
|
||||
startDate: '2026-02-16',
|
||||
endDate: '2026-02-20',
|
||||
startTime: '09:00',
|
||||
endTime: '18:00',
|
||||
location: 'Le Revel, Rogues (30)',
|
||||
distance: 142,
|
||||
participantCount: 24,
|
||||
description: 'Une semaine collaborative pour se rencontrer, co-créer et faire avancer le projet de Réseau Social Universel. Au programme : sessions plénières en intelligence collective, ateliers en forum ouvert, et randonnée au Cirque de Navacelles. Hébergement sur place au Revel, écolieu à Rogues dans le Gard.',
|
||||
hostName: 'Reconnexion',
|
||||
hostInitials: 'RC',
|
||||
themes: ['Social'],
|
||||
},
|
||||
{
|
||||
id: 'event-2',
|
||||
title: 'Atelier low-tech',
|
||||
date: 'Sam. 8 fév. · 14h00',
|
||||
startDate: '2026-02-08',
|
||||
startTime: '14:00',
|
||||
endTime: '17:00',
|
||||
location: 'La Maison du Vélo, Lyon',
|
||||
distance: 3,
|
||||
participantCount: 12,
|
||||
description: 'Un atelier pratique pour découvrir les low-tech et apprendre à fabriquer des objets du quotidien.',
|
||||
hostName: 'La Maison du Vélo',
|
||||
hostInitials: 'MV',
|
||||
themes: ['Tech', 'Nature'],
|
||||
},
|
||||
{
|
||||
id: 'event-3',
|
||||
title: 'Forum Ouvert Transition',
|
||||
date: 'Sam. 22 fév. · 9h00',
|
||||
startDate: '2026-02-22',
|
||||
startTime: '09:00',
|
||||
endTime: '18:00',
|
||||
location: "Tiers-lieu L'Hermitage",
|
||||
distance: 89,
|
||||
participantCount: 45,
|
||||
description: "Un forum ouvert sur la transition écologique et sociale, dans le tiers-lieu L'Hermitage.",
|
||||
hostName: "L'Hermitage",
|
||||
hostInitials: 'LH',
|
||||
themes: ['Social', 'Nature'],
|
||||
},
|
||||
{
|
||||
id: 'event-4',
|
||||
title: 'Formation CNV',
|
||||
date: 'Sam. 1 mars · 9h30',
|
||||
startDate: '2026-03-01',
|
||||
startTime: '09:30',
|
||||
endTime: '17:00',
|
||||
location: 'MJC Montplaisir, Lyon',
|
||||
distance: 5,
|
||||
participantCount: 16,
|
||||
description: 'Initiation à la Communication Non Violente. Venez découvrir les bases de la CNV pour améliorer vos relations.',
|
||||
hostName: 'MJC Montplaisir',
|
||||
hostInitials: 'MJ',
|
||||
themes: ['Social'],
|
||||
},
|
||||
{
|
||||
id: 'event-5',
|
||||
title: 'Rencontre des Colibris',
|
||||
date: 'Mer. 12 fév. · 19h00',
|
||||
startDate: '2026-02-12',
|
||||
startTime: '19:00',
|
||||
endTime: '21:00',
|
||||
location: "La Maison de l'Environnement",
|
||||
distance: 7,
|
||||
participantCount: 30,
|
||||
description: "Rencontre mensuelle du groupe local des Colibris pour échanger sur les projets en cours.",
|
||||
hostName: 'Les Colibris',
|
||||
hostInitials: 'LC',
|
||||
themes: ['Social', 'Nature'],
|
||||
},
|
||||
];
|
||||
|
||||
// Participations: which users are in which events
|
||||
export const seedParticipations: FpParticipationData[] = [
|
||||
// Marie (current user) participates in events 1, 2, 3
|
||||
{ id: 'part-1', eventId: 'event-1', userId: 'user-1', isConfirmed: true },
|
||||
{ id: 'part-2', eventId: 'event-2', userId: 'user-1', isConfirmed: true },
|
||||
{ id: 'part-3', eventId: 'event-3', userId: 'user-1', isConfirmed: true },
|
||||
// Jean participates in events 1
|
||||
{ id: 'part-4', eventId: 'event-1', userId: 'user-2', isConfirmed: true },
|
||||
// Thomas participates in event 1
|
||||
{ id: 'part-5', eventId: 'event-1', userId: 'user-7', isConfirmed: true },
|
||||
];
|
||||
|
||||
export const seedMeetingPoints: FpMeetingPointData[] = [
|
||||
{
|
||||
id: 'mp-1',
|
||||
eventId: 'event-1',
|
||||
location: 'Café de la Place',
|
||||
time: '30 min avant',
|
||||
hostName: 'Marie',
|
||||
hostInitials: 'MD',
|
||||
},
|
||||
{
|
||||
id: 'mp-2',
|
||||
eventId: 'event-1',
|
||||
location: 'Station de métro Bellecour',
|
||||
time: '15h30',
|
||||
hostName: 'Jean',
|
||||
hostInitials: 'JD',
|
||||
},
|
||||
];
|
||||
|
||||
export const seedFriendships: FpFriendshipData[] = [
|
||||
{ id: 'fr-1', userId: 'user-1', friendId: 'user-2' },
|
||||
{ id: 'fr-2', userId: 'user-1', friendId: 'user-3' },
|
||||
{ id: 'fr-3', userId: 'user-1', friendId: 'user-4' },
|
||||
{ id: 'fr-4', userId: 'user-1', friendId: 'user-5' },
|
||||
{ id: 'fr-5', userId: 'user-1', friendId: 'user-6' },
|
||||
{ id: 'fr-6', userId: 'user-1', friendId: 'user-10' },
|
||||
];
|
||||
@@ -0,0 +1,388 @@
|
||||
// Auto-generated by scripts/extract-step-definitions.ts
|
||||
// Do not edit manually - run "bun run steps:extract" to regenerate
|
||||
|
||||
export interface StepDefinitionInfo {
|
||||
pattern: string;
|
||||
keyword: 'Given' | 'When' | 'Then';
|
||||
file: string;
|
||||
sourceCode: string;
|
||||
lineNumber: number;
|
||||
}
|
||||
|
||||
export const stepDefinitions: StepDefinitionInfo[] = [
|
||||
{
|
||||
"pattern": "je clique sur un événement",
|
||||
"keyword": "When",
|
||||
"file": "event.steps.ts",
|
||||
"sourceCode": "When('je clique sur un événement', async function (this: FestipodWorld) {\n this.navigateTo('#/demo/event-detail');\n});",
|
||||
"lineNumber": 5
|
||||
},
|
||||
{
|
||||
"pattern": "je visualise l'événement {string}",
|
||||
"keyword": "Given",
|
||||
"file": "event.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": 9
|
||||
},
|
||||
{
|
||||
"pattern": "je peux annuler et revenir à l'écran précédent",
|
||||
"keyword": "Then",
|
||||
"file": "event.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 const source = this.getRenderedText();\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": 15
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir la liste des participants",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"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 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": 22
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir les détails de l'événement",
|
||||
"keyword": "Then",
|
||||
"file": "event.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 const source = this.getRenderedText();\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": 31
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran affiche les informations de l'événement",
|
||||
"keyword": "Then",
|
||||
"file": "event.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 const source = this.getRenderedText();\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": 41
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir la liste des événements",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"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 expect(/Mes événements à venir/.test(source), 'Home screen should have \"Événements à venir\" text').to.be.true;\n } else if (this.currentScreenId === 'events') {\n expect(/<Card[^>]*onClick/.test(source), 'Events screen should have clickable Card components').to.be.true;\n } else {\n expect.fail(`Unexpected screen \"${this.currentScreenId}\" - events list should be on home or events screen`);\n }\n});",
|
||||
"lineNumber": 51
|
||||
},
|
||||
{
|
||||
"pattern": "les événements affichent leur lieu",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"sourceCode": "Then('les événements affichent leur lieu', async function (this: FestipodWorld) {\n const source = this.getRenderedText();\n const locationPattern = /📍.*<span[^>]*className=\"user-content\"[^>]*>[^<]+<\\/span>/;\n expect(locationPattern.test(source), 'Event cards should display location text after 📍 emoji').to.be.true;\n});",
|
||||
"lineNumber": 62
|
||||
},
|
||||
{
|
||||
"pattern": "je peux m'inscrire à l'événement",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"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 const hasParticiperButton = /isJoined \\? '✓ Inscrit' : 'Participer'/.test(source);\n expect(hasParticiperButton, 'Event detail should have Participer/Inscrit toggle button').to.be.true;\n});",
|
||||
"lineNumber": 68
|
||||
},
|
||||
{
|
||||
"pattern": "je peux me désinscrire de l'événement",
|
||||
"keyword": "Then",
|
||||
"file": "event.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 const source = this.getRenderedText();\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": 75
|
||||
},
|
||||
{
|
||||
"pattern": "le formulaire contient le champ obligatoire {string}",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"sourceCode": "Then('le formulaire contient le champ obligatoire {string}', async function (this: FestipodWorld, fieldName: string) {\n expect(this.currentScreenId, 'This step is for form screens only').to.equal('create-event');\n const source = this.getRenderedText();\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": 84
|
||||
},
|
||||
{
|
||||
"pattern": "le formulaire contient les champs obligatoires suivants:",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"sourceCode": "Then('le formulaire contient les champs obligatoires suivants:', async function (this: FestipodWorld, dataTable) {\n expect(this.currentScreenId, 'This step is for form screens only').to.equal('create-event');\n const source = this.getRenderedText();\n const expectedFields = dataTable.raw().flat();\n expectedFields.forEach((fieldName: string) => {\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": 92
|
||||
},
|
||||
{
|
||||
"pattern": "le formulaire permet de détecter les doublons",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"sourceCode": "Then('le formulaire permet de détecter les doublons', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n expect(/showDuplicateWarning/.test(source), 'Form should have duplicate detection logic').to.be.true;\n expect(/Événement similaire détecté/.test(source), 'Form should have duplicate warning message').to.be.true;\n});",
|
||||
"lineNumber": 103
|
||||
},
|
||||
{
|
||||
"pattern": "le formulaire permet d'importer depuis Mobilizon ou Transiscope",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"sourceCode": "Then('le formulaire permet d\\'importer depuis Mobilizon ou Transiscope', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n expect(/importableEvents/.test(source), 'Form should have importable events data').to.be.true;\n expect(/Mobilizon/.test(source), 'Form should support Mobilizon import').to.be.true;\n expect(/Transiscope/.test(source), 'Form should support Transiscope import').to.be.true;\n expect(/Importer depuis une source externe/.test(source), 'Form should have import section').to.be.true;\n});",
|
||||
"lineNumber": 110
|
||||
},
|
||||
{
|
||||
"pattern": "l'import externe ne déclenche pas d'alerte doublon",
|
||||
"keyword": "Then",
|
||||
"file": "event.steps.ts",
|
||||
"sourceCode": "Then('l\\'import externe ne déclenche pas d\\'alerte doublon', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n expect(/importedFrom/.test(source), 'Form should track import source').to.be.true;\n expect(/&& !importedFrom/.test(source), 'Duplicate warning should be disabled for imports').to.be.true;\n});",
|
||||
"lineNumber": 119
|
||||
},
|
||||
{
|
||||
"pattern": "je peux configurer mes notifications",
|
||||
"keyword": "Then",
|
||||
"file": "home.steps.ts",
|
||||
"sourceCode": "Then('je peux configurer mes notifications', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('settings');\n const source = this.getRenderedText();\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": 5
|
||||
},
|
||||
{
|
||||
"pattern": "je clique sur un participant",
|
||||
"keyword": "When",
|
||||
"file": "user.steps.ts",
|
||||
"sourceCode": "When('je clique sur un participant', async function (this: FestipodWorld) {\n this.navigateTo('#/demo/user-profile');\n});",
|
||||
"lineNumber": 5
|
||||
},
|
||||
{
|
||||
"pattern": "je visualise le profil de {string}",
|
||||
"keyword": "Given",
|
||||
"file": "user.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": 9
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir mon profil",
|
||||
"keyword": "Then",
|
||||
"file": "user.steps.ts",
|
||||
"sourceCode": "Then('je peux voir mon profil', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('profile');\n const source = this.getRenderedText();\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": 15
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir le profil de l'utilisateur",
|
||||
"keyword": "Then",
|
||||
"file": "user.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 source = this.getRenderedText();\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": 23
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran affiche les informations du profil",
|
||||
"keyword": "Then",
|
||||
"file": "user.steps.ts",
|
||||
"sourceCode": "Then('l\\'écran affiche les informations du profil', async function (this: FestipodWorld) {\n const source = this.getRenderedText();\n if (this.currentScreenId === 'profile') {\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 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": 31
|
||||
},
|
||||
{
|
||||
"pattern": "je peux contacter l'utilisateur",
|
||||
"keyword": "Then",
|
||||
"file": "user.steps.ts",
|
||||
"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 const hasContactButton = /<Button>Contacter<\\/Button>/.test(source);\n expect(hasContactButton, 'User profile should have \"Contacter\" button').to.be.true;\n});",
|
||||
"lineNumber": 46
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir les événements auxquels l'utilisateur a participé",
|
||||
"keyword": "Then",
|
||||
"file": "user.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 const source = this.getRenderedText();\n expect(/Événements à venir/.test(source), 'User profile should have \"Événements à venir\" section').to.be.true;\n expect(/Événements passés/.test(source), 'User profile should have \"Événements passés\" section').to.be.true;\n});",
|
||||
"lineNumber": 53
|
||||
},
|
||||
{
|
||||
"pattern": "les événements affichent leur localisation et distance",
|
||||
"keyword": "Then",
|
||||
"file": "user.steps.ts",
|
||||
"sourceCode": "Then('les événements affichent leur localisation et distance', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const source = this.getRenderedText();\n expect(/location: '[^']+'/.test(source), 'Events should have location data').to.be.true;\n expect(/distance: \\d+/.test(source), 'Events should have distance data').to.be.true;\n expect(/\\{event\\.location\\}/.test(source), 'Events should render location').to.be.true;\n expect(/\\{event\\.distance\\}/.test(source), 'Events should render distance').to.be.true;\n});",
|
||||
"lineNumber": 60
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir le QR code",
|
||||
"keyword": "Then",
|
||||
"file": "user.steps.ts",
|
||||
"sourceCode": "Then('je peux voir le QR code', async function (this: FestipodWorld) {\n const source = this.getRenderedText();\n if (this.currentScreenId === 'share-profile') {\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 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 expect.fail(`QR code should be on share-profile or meeting-points, not \"${this.currentScreenId}\"`);\n }\n});",
|
||||
"lineNumber": 69
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir le lien de partage",
|
||||
"keyword": "Then",
|
||||
"file": "user.steps.ts",
|
||||
"sourceCode": "Then('je peux voir le lien de partage', async function (this: FestipodWorld) {\n expect(this.currentScreenId, 'Share link should be on share-profile screen').to.equal('share-profile');\n const source = this.getRenderedText();\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});",
|
||||
"lineNumber": 82
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran {string} est affiché",
|
||||
"keyword": "Given",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Given('l\\'écran {string} est affiché', async function (this: FestipodWorld, screenName: string) {\n const screenId = screenName.toLowerCase().replace(/ /g, '-');\n this.navigateTo(`#/demo/${screenId}`);\n});",
|
||||
"lineNumber": 5
|
||||
},
|
||||
{
|
||||
"pattern": "le formulaire de création est vide",
|
||||
"keyword": "Given",
|
||||
"file": "form.steps.ts",
|
||||
"sourceCode": "Given('le formulaire de création est vide', async function (this: FestipodWorld) {\n this.formFields.forEach((field, key) => {\n this.formFields.set(key, { ...field, value: '' });",
|
||||
"lineNumber": 10
|
||||
},
|
||||
{
|
||||
"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 source = this.getRenderedText();\n const escapedName = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\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": 16
|
||||
},
|
||||
{
|
||||
"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 source = this.getRenderedText();\n const escapedName = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`>${escapedName}[^<]*<`);\n expect(pattern.test(source), `Field \"${fieldName}\" should be present in screen`).to.be.true;\n});",
|
||||
"lineNumber": 25
|
||||
},
|
||||
{
|
||||
"pattern": "Scénario non implémenté",
|
||||
"keyword": "Given",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Given('Scénario non implémenté', async function (this: FestipodWorld) {\n return 'skipped';\n});",
|
||||
"lineNumber": 7
|
||||
},
|
||||
{
|
||||
"pattern": "je suis sur la page {string}",
|
||||
"keyword": "Given",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Given('je suis sur la page {string}', async function (this: FestipodWorld, pageName: string) {\n const screenId = resolveScreenId(pageName);\n this.navigateTo(`#/demo/${screenId}`);\n});",
|
||||
"lineNumber": 38
|
||||
},
|
||||
{
|
||||
"pattern": "je suis connecté en tant qu'utilisateur",
|
||||
"keyword": "Given",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Given('je suis connecté en tant qu\\'utilisateur', async function (this: FestipodWorld) {\n this.isAuthenticated = true;\n});",
|
||||
"lineNumber": 43
|
||||
},
|
||||
{
|
||||
"pattern": "je suis connecté",
|
||||
"keyword": "Given",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Given('je suis connecté', async function (this: FestipodWorld) {\n this.isAuthenticated = true;\n});",
|
||||
"lineNumber": 47
|
||||
},
|
||||
{
|
||||
"pattern": "je ne suis pas connecté",
|
||||
"keyword": "Given",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Given('je ne suis pas connecté', async function (this: FestipodWorld) {\n this.isAuthenticated = false;\n});",
|
||||
"lineNumber": 51
|
||||
},
|
||||
{
|
||||
"pattern": "je navigue vers {string}",
|
||||
"keyword": "When",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "When('je navigue vers {string}', async function (this: FestipodWorld, pageName: string) {\n const screenId = resolveScreenId(pageName);\n this.navigateTo(`#/demo/${screenId}`);\n});",
|
||||
"lineNumber": 55
|
||||
},
|
||||
{
|
||||
"pattern": "je clique sur {string}",
|
||||
"keyword": "When",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "When('je clique sur {string}', async function (this: FestipodWorld, elementName: string) {\n const source = this.getRenderedText();\n const escapedName = elementName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`onClick[^>]*>[^<]*${escapedName}`, 'i');\n expect(pattern.test(source), `Clickable element \"${elementName}\" should exist in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 60
|
||||
},
|
||||
{
|
||||
"pattern": "je sélectionne {string}",
|
||||
"keyword": "When",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "When('je sélectionne {string}', async function (this: FestipodWorld, elementName: string) {\n const source = this.getRenderedText();\n const escapedName = elementName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`onClick[^>]*>[^<]*${escapedName}`, 'i');\n expect(pattern.test(source), `Selectable element \"${elementName}\" should exist in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 67
|
||||
},
|
||||
{
|
||||
"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 const source = this.getRenderedText();\n const escapedName = buttonName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`<Button[^>]*>[^<]*${escapedName}[^<]*</Button>`, 'i');\n expect(pattern.test(source), `Button \"${buttonName}\" should exist in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 74
|
||||
},
|
||||
{
|
||||
"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": 81
|
||||
},
|
||||
{
|
||||
"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": 86
|
||||
},
|
||||
{
|
||||
"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": 91
|
||||
},
|
||||
{
|
||||
"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 expect(this.hasText(sectionName), `Section \"${sectionName}\" should be present in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 96
|
||||
},
|
||||
{
|
||||
"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 const source = this.getRenderedText();\n const pattern = new RegExp(`navigate\\\\s*\\\\(\\\\s*['\"]${screenId}['\"]\\\\s*\\\\)`);\n expect(pattern.test(source), `Navigation to \"${screenId}\" should exist in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 100
|
||||
},
|
||||
{
|
||||
"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 const source = this.getRenderedText();\n const escapedItem = menuItem.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`label:\\\\s*['\"]${escapedItem}['\"][^}]*active:\\\\s*true`, 'i');\n expect(pattern.test(source), `Menu item \"${menuItem}\" should be active in NavBar of screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 107
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran contient un bouton {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('l\\'écran contient un bouton {string}', async function (this: FestipodWorld, buttonText: string) {\n expect(this.hasText(buttonText), `Button \"${buttonText}\" should be present in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 114
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran contient un champ {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('l\\'écran contient un champ {string}', async function (this: FestipodWorld, fieldLabel: string) {\n expect(this.hasText(fieldLabel), `Field \"${fieldLabel}\" should be present in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 118
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran contient un texte {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('l\\'écran contient un texte {string}', async function (this: FestipodWorld, text: string) {\n expect(this.hasText(text), `Text \"${text}\" should be present in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 122
|
||||
},
|
||||
{
|
||||
"pattern": "l'écran contient un avatar",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('l\\'écran contient un avatar', async function (this: FestipodWorld) {\n const source = this.getRenderedText();\n const hasAvatar = /<Avatar/.test(source);\n expect(hasAvatar, `Avatar should be present in screen \"${this.currentScreenId}\"`).to.be.true;\n});",
|
||||
"lineNumber": 126
|
||||
},
|
||||
{
|
||||
"pattern": "je peux voir la section {string}",
|
||||
"keyword": "Then",
|
||||
"file": "navigation.steps.ts",
|
||||
"sourceCode": "Then('je peux voir la section {string}', async function (this: FestipodWorld, sectionName: string) {\n const source = this.getRenderedText();\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": 132
|
||||
}
|
||||
];
|
||||
|
||||
export function findStepDefinition(stepText: string): StepDefinitionInfo | null {
|
||||
for (const def of stepDefinitions) {
|
||||
// Convert Cucumber expression to regex
|
||||
// {string} -> "[^"]+"
|
||||
// {int} -> \\d+
|
||||
const regexPattern = def.pattern
|
||||
.replace(/\{string\}/g, '"[^"]+"')
|
||||
.replace(/\{int\}/g, '\\d+');
|
||||
|
||||
try {
|
||||
const regex = new RegExp(regexPattern);
|
||||
if (regex.test(stepText)) {
|
||||
return def;
|
||||
}
|
||||
} catch {
|
||||
// If pattern fails, try simple includes
|
||||
const simplified = def.pattern.replace(/\{string\}/g, '').replace(/\{int\}/g, '').trim();
|
||||
if (stepText.includes(simplified)) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,837 @@
|
||||
// Auto-generated by scripts/parse-test-results.ts
|
||||
// Do not edit manually - run "bun run test:results" to regenerate
|
||||
import type { FeatureTestStatus, ScenarioTestResult } from '../types/gherkin';
|
||||
|
||||
interface RawFeatureTestStatus {
|
||||
featureId: string;
|
||||
totalScenarios: number;
|
||||
passed: number;
|
||||
failed: number;
|
||||
skipped: number;
|
||||
lastRun?: string;
|
||||
scenarios?: ScenarioTestResult[];
|
||||
}
|
||||
|
||||
const rawResults: RawFeatureTestStatus[] = [
|
||||
{
|
||||
"featureId": "us-13",
|
||||
"totalScenarios": 10,
|
||||
"passed": 7,
|
||||
"failed": 0,
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-26T17:31:35.878Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au formulaire de relai d'événement",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les champs obligatoires du formulaire",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier la présence du bouton de relai",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Pouvoir annuler le relai d'événement",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Détecter un événement similaire déjà relayé",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Importer un événement depuis une source externe",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Pas d'alerte doublon lors d'un import externe",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Modifier un événement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Supprimer un événement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Retirer une organisation (personne ou structure)",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-3",
|
||||
"totalScenarios": 3,
|
||||
"passed": 3,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-26T17:31:35.878Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux détails d'un événement terminé",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir la description de l'événement",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir la liste des participants",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-5",
|
||||
"totalScenarios": 5,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 5,
|
||||
"lastRun": "2026-01-26T17:31:35.878Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Voir les commentaires existants",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Ajouter un commentaire",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Modifier un commentaire",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Supprimer un commentaire",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Enregistrer les interactions avec des individus (Date/Heure/Lieu)",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-7",
|
||||
"totalScenarios": 6,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-26T17:31:35.878Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Consulter un événement avant inscription",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "S'inscrire à un événement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Se désinscrire d'un événement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Rechercher un événement existant",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'écran",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Rechercher dans une base existante (Mobilizon)",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-8",
|
||||
"totalScenarios": 8,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 8,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Consulter un macro-événement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les événements rattachés",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Rattacher un événement existant",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir la consolidation des participants",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Créer un macro-événement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir la consolidation des commentaires/liens/ressources",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Rattacher à une thématique particulière",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Gérer un événement répété sur plusieurs périodes",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-16",
|
||||
"totalScenarios": 4,
|
||||
"passed": 4,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux points de rencontre",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir le bouton pour proposer un point de rencontre",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Ouvrir le formulaire de proposition",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Définir l'heure de rencontre",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-17",
|
||||
"totalScenarios": 5,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 5,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Partager un événement auquel je participe",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Informer les utilisateurs à proximité",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Informer les utilisateurs par thématique",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Informer mes abonnés",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Combiner les options de notification",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-18",
|
||||
"totalScenarios": 4,
|
||||
"passed": 1,
|
||||
"failed": 0,
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Configurer les notifications de nouveaux participants",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Activer les notifications pour un événement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Filtrer les notifications par réseau",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les nouveaux participants sur l'accueil",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-19",
|
||||
"totalScenarios": 5,
|
||||
"passed": 3,
|
||||
"failed": 0,
|
||||
"skipped": 2,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Voir les événements à venir sur l'accueil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir le récapitulatif par période",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les événements proches géographiquement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir mes inscriptions",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'accueil",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-10",
|
||||
"totalScenarios": 6,
|
||||
"passed": 5,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au profil d'un participant",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir les événements du participant",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir la localisation des événements",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir le formulaire de contact",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les informations du profil",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les détails du profil utilisateur",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-12",
|
||||
"totalScenarios": 7,
|
||||
"passed": 3,
|
||||
"failed": 0,
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la liste des événements depuis le profil",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à la liste des événements depuis découvrir",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Filtrer par date",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Filtrer par personne",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'écran événements",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données de l'écran profil",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir la vue carte des événements",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-15",
|
||||
"totalScenarios": 5,
|
||||
"passed": 3,
|
||||
"failed": 0,
|
||||
"skipped": 2,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la liste des inscrits d'un événement",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à la liste des inscrits d'un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir la liste des participants d'un événement",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir la liste des participants d'un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Cliquer sur un inscrit pour voir son profil",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-20",
|
||||
"totalScenarios": 6,
|
||||
"passed": 5,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à mon profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir mon réseau",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir un profil de mon réseau",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Consulter un événement depuis un profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données du profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir les profils publiques",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-21",
|
||||
"totalScenarios": 5,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux paramètres de profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Configurer la visibilité du profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Rendre le profil public",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données des paramètres",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données du profil",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-22",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au partage de profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Naviguer vers le partage de profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir le QR code de parrainage",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir le lien de parrainage",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir les statistiques de parrainage",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-23",
|
||||
"totalScenarios": 5,
|
||||
"passed": 5,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au partage depuis le profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir le QR code",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir le lien de partage",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à l'écran de partage dédié",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les données du profil",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-24",
|
||||
"totalScenarios": 3,
|
||||
"passed": 2,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux paramètres de notification",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Configurer les notifications de contacts",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Voir les activités de mes contacts sur l'accueil",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-25",
|
||||
"totalScenarios": 3,
|
||||
"passed": 1,
|
||||
"failed": 0,
|
||||
"skipped": 2,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux paramètres de notification",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Configurer le rayon de notification",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Configurer les thématiques d'intérêt",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-26",
|
||||
"totalScenarios": 4,
|
||||
"passed": 3,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au formulaire de relai d'événement",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Définir le rayon d'intérêt",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Choisir une thématique",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les champs obligatoires",
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-9",
|
||||
"totalScenarios": 5,
|
||||
"passed": 4,
|
||||
"failed": 0,
|
||||
"skipped": 1,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au profil pour voir la photo",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Naviguer vers le profil depuis la liste des participants",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Consulter la liste des inscrits à un atelier",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les champs de données du profil",
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"name": "Ajouter une photo personnelle sur une fiche existante",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-1",
|
||||
"totalScenarios": 5,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 5,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder aux détails d'un événement terminé",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Consulter la liste des participants d'un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Consulter les ressources d'un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Consulter le programme détaillé par journée/heure",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à la zone de partage collective",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-11",
|
||||
"totalScenarios": 3,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 3,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder au bilan consolidé",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les commentaires regroupés par atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir la synthèse globale",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-14",
|
||||
"totalScenarios": 7,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 7,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la création d'atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Vérifier les champs obligatoires pour créer un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Créer un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Modifier un atelier existant",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Supprimer un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Sélectionner mon événement parent",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Définir les horaires de fin de l'atelier",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-2",
|
||||
"totalScenarios": 5,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 5,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Accéder à la zone de notes personnelles",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à la zone de partage publique",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Ajouter une note personnelle",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Ajouter un lien/ressource",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Consulter le programme détaillé des ateliers par journée/heure",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-4",
|
||||
"totalScenarios": 5,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 5,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Voir les commentaires existants d'un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Ajouter un commentaire à un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Modifier un commentaire existant",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Supprimer un commentaire",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Accéder à l'icône ajouter un commentaire",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureId": "us-6",
|
||||
"totalScenarios": 4,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"skipped": 4,
|
||||
"lastRun": "2026-01-26T17:31:35.879Z",
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "Voir les ateliers d'un événement",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Voir les personnes pré-inscrites à un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "S'inscrire à un atelier",
|
||||
"status": "skipped"
|
||||
},
|
||||
{
|
||||
"name": "Se désinscrire d'un atelier",
|
||||
"status": "skipped"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const testResults: Map<string, FeatureTestStatus> = new Map(
|
||||
rawResults.map(r => [r.featureId, { ...r, lastRun: r.lastRun ? new Date(r.lastRun) : undefined }])
|
||||
);
|
||||
|
||||
export function getTestStatus(featureId: string): FeatureTestStatus | undefined {
|
||||
return testResults.get(featureId);
|
||||
}
|
||||
|
||||
export function getScenarioResults(featureId: string): ScenarioTestResult[] {
|
||||
return testResults.get(featureId)?.scenarios ?? [];
|
||||
}
|
||||
|
||||
export function getAllTestResults(): FeatureTestStatus[] {
|
||||
return Array.from(testResults.values());
|
||||
}
|
||||
|
||||
export function getTestSummary() {
|
||||
const results = getAllTestResults();
|
||||
const firstResult = results[0];
|
||||
return {
|
||||
totalFeatures: results.length,
|
||||
totalScenarios: results.reduce((acc, r) => acc + r.totalScenarios, 0),
|
||||
passed: results.reduce((acc, r) => acc + r.passed, 0),
|
||||
failed: results.reduce((acc, r) => acc + r.failed, 0),
|
||||
skipped: results.reduce((acc, r) => acc + r.skipped, 0),
|
||||
lastRun: firstResult?.lastRun,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* App-level data types used by screens.
|
||||
* These are plain TypeScript types (not RDF-bound).
|
||||
* The data layer maps to/from NextGraph shapes internally.
|
||||
*/
|
||||
|
||||
export interface FpEventData {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
location: string;
|
||||
distance?: number;
|
||||
participantCount: number;
|
||||
coverImage?: string;
|
||||
hostName?: string;
|
||||
hostInitials?: string;
|
||||
themes?: string[];
|
||||
}
|
||||
|
||||
export interface FpUserData {
|
||||
id: string;
|
||||
name: string;
|
||||
initials: string;
|
||||
username: string;
|
||||
role?: string;
|
||||
isPublic?: boolean;
|
||||
bio?: string;
|
||||
city?: string;
|
||||
eventsCount?: number;
|
||||
friendsCount?: number;
|
||||
participationsCount?: number;
|
||||
}
|
||||
|
||||
export interface FpParticipationData {
|
||||
id: string;
|
||||
eventId: string;
|
||||
userId: string;
|
||||
isConfirmed: boolean;
|
||||
}
|
||||
|
||||
export interface FpMeetingPointData {
|
||||
id: string;
|
||||
eventId: string;
|
||||
location: string;
|
||||
time: string;
|
||||
hostName: string;
|
||||
hostInitials: string;
|
||||
}
|
||||
|
||||
export interface FpFriendshipData {
|
||||
id: string;
|
||||
userId: string;
|
||||
friendId: string;
|
||||
}
|
||||
Reference in New Issue
Block a user