- {/* Toolbar */}
-
-
-
-
-
- {filePath && (
-
- {filePath}
-
+
+ {/* Toolbar */}
+
+
-
- {/* Content */}
-
-
-
- {blocks.map((block, blockIndex) => (
- toggleBlock(blockIndex)}
- showDefinitions={showDefinitions}
- />
- ))}
-
-
-
+
+
-
+
+ {/* Scenario/Background Blocks */}
+ {blocks.filter(b => b.type !== 'header').map((block, blockIndex) => (
+
toggleBlock(blocks.indexOf(block))}
+ showDefinitions={showDefinitions}
+ />
+ ))}
+
);
}
@@ -220,89 +206,371 @@ interface BlockRendererProps {
function BlockRenderer({ block, isCollapsed, onToggle, showDefinitions }: BlockRendererProps) {
if (block.type === 'header') {
- return (
- <>
- {block.lines.map((line, index) => (
-
- ))}
- >
- );
+ // Header is now handled separately in the main component
+ return null;
}
- const firstLine = block.lines[0] ?? '';
const restLines = block.lines.slice(1);
- const isCollapsible = block.type === 'scenario' || block.type === 'background';
+ const isBackground = block.type === 'background';
+
+ // Parse steps from rest lines
+ const parsedSteps = parseStepsFromLines(restLines);
+
+ // Determine border color based on status
+ const borderColor = block.status === 'passed' ? 'border-l-green-500' :
+ block.status === 'failed' ? 'border-l-red-500' :
+ block.status === 'skipped' ? 'border-l-yellow-500' :
+ isBackground ? 'border-l-zinc-400' : 'border-l-cyan-500';
+
+ // Status icon
+ const StatusIcon = () => {
+ if (!block.status || block.status === 'unknown') return null;
+ if (block.status === 'passed') return
;
+ if (block.status === 'failed') return
;
+ if (block.status === 'skipped') return
;
+ return
;
+ };
return (
- <>
- {/* Scenario/Background header line */}
-
+ {/* Clickable header */}
+
-
- {isCollapsible && (
-
- {isCollapsed ? : }
+
+
+ {isCollapsed ? : }
+
+
+
+
+ {isBackground ? 'Contexte' : 'Scénario'}
+
+
+ {block.name}
+
+
+ {parsedSteps.length > 0 && (
+
+ {parsedSteps.length} étapes
)}
- {block.status && block.status !== 'unknown' && (
-
- )}
- {highlightLine(firstLine, false)}
-
-
+
+
{/* Collapsible content */}
- {!isCollapsed && restLines.map((line, index) => (
-
- ))}
+ {!isCollapsed && (
+
+
+ {parsedSteps.map((step, index) => (
+
+ ))}
+
- {/* Error message for failed scenarios */}
- {!isCollapsed && block.status === 'failed' && block.errorMessage && (
-
-
Erreur:
-
- {block.errorMessage}
-
-
+ {/* Error message for failed scenarios */}
+ {block.status === 'failed' && block.errorMessage && (
+
+
Erreur:
+
+ {block.errorMessage}
+
+
+ )}
+
)}
- >
+
);
}
-interface LineRendererProps {
- line: string;
+interface ParsedStep {
+ type: 'given' | 'when' | 'then' | 'and' | 'examples' | 'table' | 'other';
+ keyword: string;
+ text: string;
+ originalLine: string;
+ tableRows?: string[][];
+}
+
+function parseStepsFromLines(lines: string[]): ParsedStep[] {
+ const steps: ParsedStep[] = [];
+ let currentStep: ParsedStep | null = null;
+
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (!trimmed) continue;
+
+ // Check for table row
+ if (trimmed.startsWith('|')) {
+ const cells = trimmed.split('|').slice(1, -1).map(c => c.trim());
+ if (currentStep) {
+ if (!currentStep.tableRows) currentStep.tableRows = [];
+ currentStep.tableRows.push(cells);
+ } else {
+ // Standalone table row (shouldn't happen, but handle it)
+ steps.push({
+ type: 'table',
+ keyword: '',
+ text: trimmed,
+ originalLine: line,
+ tableRows: [cells]
+ });
+ }
+ continue;
+ }
+
+ // Check for step keywords
+ let matched = false;
+
+ for (const kw of keywords.given) {
+ if (trimmed.startsWith(kw)) {
+ if (currentStep) steps.push(currentStep);
+ currentStep = { type: 'given', keyword: kw, text: trimmed.slice(kw.length).trim(), originalLine: line };
+ matched = true;
+ break;
+ }
+ }
+ if (!matched) {
+ for (const kw of keywords.when) {
+ if (trimmed.startsWith(kw)) {
+ if (currentStep) steps.push(currentStep);
+ currentStep = { type: 'when', keyword: kw, text: trimmed.slice(kw.length).trim(), originalLine: line };
+ matched = true;
+ break;
+ }
+ }
+ }
+ if (!matched) {
+ for (const kw of keywords.then) {
+ if (trimmed.startsWith(kw)) {
+ if (currentStep) steps.push(currentStep);
+ currentStep = { type: 'then', keyword: kw, text: trimmed.slice(kw.length).trim(), originalLine: line };
+ matched = true;
+ break;
+ }
+ }
+ }
+ if (!matched) {
+ for (const kw of keywords.and) {
+ if (trimmed.startsWith(kw)) {
+ if (currentStep) steps.push(currentStep);
+ currentStep = { type: 'and', keyword: kw, text: trimmed.slice(kw.length).trim(), originalLine: line };
+ matched = true;
+ break;
+ }
+ }
+ }
+ if (!matched) {
+ for (const kw of keywords.examples) {
+ if (trimmed.startsWith(kw)) {
+ if (currentStep) steps.push(currentStep);
+ currentStep = { type: 'examples', keyword: kw, text: trimmed.slice(kw.length).trim(), originalLine: line };
+ matched = true;
+ break;
+ }
+ }
+ }
+ if (!matched && trimmed) {
+ if (currentStep) steps.push(currentStep);
+ currentStep = { type: 'other', keyword: '', text: trimmed, originalLine: line };
+ }
+ }
+
+ if (currentStep) steps.push(currentStep);
+
+ return steps;
+}
+
+interface StepRendererProps {
+ step: ParsedStep;
showDefinitions: boolean;
}
-function LineRenderer({ line, showDefinitions }: LineRendererProps) {
- const trimmed = line.trim();
- const isStep = [...keywords.given, ...keywords.when, ...keywords.then, ...keywords.and]
- .some(kw => trimmed.startsWith(kw));
+function StepRenderer({ step, showDefinitions }: StepRendererProps) {
+ // Always check for step definition to show dotted underline
+ const stepDef = step.type !== 'table' && step.type !== 'other' && step.type !== 'examples'
+ ? findStepDefinition(step.originalLine.trim())
+ : null;
- const stepDef = isStep && showDefinitions ? findStepDefinition(trimmed) : null;
+ // Keyword colors
+ const keywordColor = step.type === 'given' ? 'text-blue-600 dark:text-blue-400' :
+ step.type === 'when' ? 'text-amber-600 dark:text-amber-400' :
+ step.type === 'then' ? 'text-green-600 dark:text-green-400' :
+ step.type === 'and' ? 'text-zinc-500 dark:text-zinc-400' :
+ step.type === 'examples' ? 'text-purple-600 dark:text-purple-400' :
+ 'text-muted-foreground';
+
+ const keywordBg = step.type === 'given' ? 'bg-blue-50 dark:bg-blue-950/30' :
+ step.type === 'when' ? 'bg-amber-50 dark:bg-amber-950/30' :
+ step.type === 'then' ? 'bg-green-50 dark:bg-green-950/30' :
+ step.type === 'and' ? 'bg-zinc-50 dark:bg-zinc-800/50' :
+ step.type === 'examples' ? 'bg-purple-50 dark:bg-purple-950/30' :
+ '';
+
+ if (step.type === 'table') {
+ return (
+
+ );
+ }
+
+ // Show popover only when definitions mode is active, but always show dotted underline for steps with definitions
+ const dottedUnderlineStyle = {
+ borderBottom: '1.3px dashed',
+ borderColor: 'rgb(161 161 170)', // zinc-400
+ };
+ const stepTextElement = stepDef ? (
+ showDefinitions ? (
+
+ {highlightStringsInText(step.text)}
+
+ ) : (
+
+ {highlightStringsInText(step.text)}
+
+ )
+ ) : (
+
{highlightStringsInText(step.text)}
+ );
return (
-
- {highlightLineWithTooltip(line, stepDef)}
+
+
+ {step.keyword && (
+
+ {step.keyword}
+
+ )}
+
+ {stepTextElement}
+
+
+ {/* Render table if present */}
+ {step.tableRows && step.tableRows.length > 0 && (
+
+
+
+ {step.tableRows.map((row, rowIndex) => (
+
+ {row.map((cell, cellIndex) => (
+ |
+ {cell}
+ |
+ ))}
+
+ ))}
+
+
+
+ )}
);
}
+function highlightStringsInText(text: string): React.ReactNode {
+ const parts: React.ReactNode[] = [];
+ let lastIndex = 0;
+ const regex = /"[^"]*"/g;
+ let match;
+
+ while ((match = regex.exec(text)) !== null) {
+ if (match.index > lastIndex) {
+ parts.push(
{text.slice(lastIndex, match.index)});
+ }
+ parts.push(
+
+ {match[0]}
+
+ );
+ lastIndex = regex.lastIndex;
+ }
+
+ if (lastIndex < text.length) {
+ parts.push(
{text.slice(lastIndex)});
+ }
+
+ return parts.length > 0 ? <>{parts}> : text;
+}
+
+// Click-based popover for step definitions (works on mobile and desktop)
+function StepDefinitionPopover({
+ stepDef,
+ children
+}: {
+ stepDef: StepDefinitionInfo;
+ children: React.ReactNode;
+}) {
+ const [isOpen, setIsOpen] = useState(false);
+ const triggerRef = useRef
(null);
+ const popoverRef = useRef(null);
+
+ // Close on click outside
+ useEffect(() => {
+ if (!isOpen) return;
+
+ const handleClickOutside = (e: MouseEvent) => {
+ if (
+ popoverRef.current &&
+ !popoverRef.current.contains(e.target as Node) &&
+ triggerRef.current &&
+ !triggerRef.current.contains(e.target as Node)
+ ) {
+ setIsOpen(false);
+ }
+ };
+
+ // Close on escape key
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') setIsOpen(false);
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ document.addEventListener('keydown', handleEscape);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ document.removeEventListener('keydown', handleEscape);
+ };
+ }, [isOpen]);
+
+ const dottedUnderlineStyle = {
+ borderBottom: '1.3px dashed rgb(161 161 170)', // zinc-400
+ };
+
+ return (
+
+ setIsOpen(!isOpen)}
+ className="cursor-pointer"
+ style={dottedUnderlineStyle}
+ >
+ {children}
+
+ {isOpen && (
+
+
+
+ )}
+
+ );
+}
+
function SourceCodePopup({ stepDef }: { stepDef: StepDefinitionInfo }) {
const lines = stepDef.sourceCode.split('\n');
@@ -402,172 +670,3 @@ function highlightTypeScript(code: string): React.ReactNode {
return <>{parts}>;
}
-
-function highlightLine(line: string, hasDefinition: boolean): React.ReactNode {
- const trimmed = line.trim();
-
- // Check for tags
- if (trimmed.startsWith('@')) {
- return {line};
- }
-
- // Check for comments
- if (trimmed.startsWith('#') && !trimmed.includes('language:')) {
- return {line};
- }
-
- // Check for language declaration
- if (trimmed.includes('# language:')) {
- return {line};
- }
-
- // Check for feature keywords
- for (const kw of keywords.feature) {
- if (trimmed.startsWith(kw)) {
- return highlightKeyword(line, kw, 'text-purple-400 font-semibold', hasDefinition);
- }
- }
-
- // Check for background
- for (const kw of keywords.background) {
- if (trimmed.startsWith(kw)) {
- return highlightKeyword(line, kw, 'text-zinc-400 font-medium', hasDefinition);
- }
- }
-
- // Check for scenario keywords
- for (const kw of keywords.scenario) {
- if (trimmed.startsWith(kw)) {
- return highlightKeyword(line, kw, 'text-cyan-400 font-medium', hasDefinition);
- }
- }
-
- // Check for step keywords
- for (const kw of [...keywords.given, ...keywords.when, ...keywords.then, ...keywords.and]) {
- if (trimmed.startsWith(kw)) {
- const color = keywords.given.includes(kw) ? 'text-blue-400' :
- keywords.when.includes(kw) ? 'text-amber-400' :
- keywords.then.includes(kw) ? 'text-green-400' :
- 'text-zinc-400';
- return highlightKeyword(line, kw, color, hasDefinition);
- }
- }
-
- // Check for examples
- for (const kw of keywords.examples) {
- if (trimmed.startsWith(kw)) {
- return highlightKeyword(line, kw, 'text-zinc-400 font-medium', hasDefinition);
- }
- }
-
- // Check for table rows
- if (trimmed.startsWith('|')) {
- return {line};
- }
-
- return {line};
-}
-
-function highlightLineWithTooltip(line: string, stepDef: StepDefinitionInfo | null): React.ReactNode {
- const trimmed = line.trim();
-
- // Find which step keyword this line starts with
- const allStepKeywords = [...keywords.given, ...keywords.when, ...keywords.then, ...keywords.and];
- let matchedKeyword: string | null = null;
- for (const kw of allStepKeywords) {
- if (trimmed.startsWith(kw)) {
- matchedKeyword = kw;
- break;
- }
- }
-
- // If no step keyword or no definition, use regular highlighting
- if (!matchedKeyword || !stepDef) {
- return highlightLine(line, false);
- }
-
- // Split the line: indentation + keyword + step text
- const index = line.indexOf(matchedKeyword);
- const before = line.slice(0, index);
- const after = line.slice(index + matchedKeyword.length);
-
- // Determine keyword color
- const color = keywords.given.includes(matchedKeyword) ? 'text-blue-400' :
- keywords.when.includes(matchedKeyword) ? 'text-amber-400' :
- keywords.then.includes(matchedKeyword) ? 'text-green-400' :
- 'text-zinc-400';
-
- // Separate leading space from the step text
- const leadingSpaceMatch = after.match(/^(\s*)/);
- const leadingSpace = leadingSpaceMatch?.[1] ?? '';
- const stepText = after.slice(leadingSpace.length);
-
- // Wrap only the step text (after keyword and space) with tooltip
- const stepTextElement = (
-
-
-
- {highlightStrings(stepText)}
-
-
-
-
-
-
- );
-
- return (
-
- {before}
- {matchedKeyword}
- {leadingSpace}
- {stepTextElement}
-
- );
-}
-
-function highlightKeyword(line: string, keyword: string, className: string, hasDefinition: boolean): React.ReactNode {
- const index = line.indexOf(keyword);
- const before = line.slice(0, index);
- const after = line.slice(index + keyword.length);
-
- return (
-
- {before}
- {keyword}
-
- {highlightStrings(after)}
-
-
- );
-}
-
-function highlightStrings(text: string): React.ReactNode {
- const parts: React.ReactNode[] = [];
- let lastIndex = 0;
- const regex = /"[^"]*"/g;
- let match;
-
- while ((match = regex.exec(text)) !== null) {
- if (match.index > lastIndex) {
- parts.push({text.slice(lastIndex, match.index)});
- }
- parts.push(
-
- {match[0]}
-
- );
- lastIndex = regex.lastIndex;
- }
-
- if (lastIndex < text.length) {
- parts.push({text.slice(lastIndex)});
- }
-
- return parts.length > 0 ? <>{parts}> : text;
-}
diff --git a/src/data/stepDefinitions.ts b/src/data/stepDefinitions.ts
index fd790ff..e3828da 100644
--- a/src/data/stepDefinitions.ts
+++ b/src/data/stepDefinitions.ts
@@ -18,7 +18,7 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 31
},
{
- "pattern": "je suis connecté en tant qu\\",
+ "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});",
@@ -88,7 +88,7 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 73
},
{
- "pattern": "je vois l\\",
+ "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});",
@@ -102,28 +102,35 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 83
},
{
- "pattern": "l\\",
+ "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.currentScreenId).to.not.be.null;\n this.attach(`Verified section: ${sectionName}`, 'text/plain');\n});",
+ "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
},
+ {
+ "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
+ },
{
"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": 93
+ "lineNumber": 101
},
{
"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": 98
+ "lineNumber": 106
},
{
- "pattern": "l\\",
+ "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});",
@@ -214,7 +221,7 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 6
},
{
- "pattern": "je peux voir les détails de l\\",
+ "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});",
@@ -242,7 +249,7 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 36
},
{
- "pattern": "je peux voir le profil de l\\",
+ "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});",
@@ -284,7 +291,7 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 74
},
{
- "pattern": "je visualise l\\",
+ "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});",
@@ -298,14 +305,14 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 85
},
{
- "pattern": "l\\",
+ "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
},
{
- "pattern": "l\\",
+ "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});",
@@ -347,28 +354,28 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 152
},
{
- "pattern": "je peux m\\",
+ "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
},
{
- "pattern": "je peux me désinscrire de l\\",
+ "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
},
{
- "pattern": "je peux contacter l\\",
+ "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
},
{
- "pattern": "je peux voir les événements auxquels l\\",
+ "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});",
@@ -389,7 +396,7 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"lineNumber": 198
},
{
- "pattern": "je peux définir mes thématiques d\\",
+ "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});",