Files
festipod/scripts/extract-step-definitions.ts
T
Sylvain Duchesne a19bda44e1 Fix step definition popup for mobile and escaped quotes
- Replace hover-based Tooltip with click-based popover for mobile support
- Fix pattern extraction regex to handle escaped apostrophes (e.g., l'écran)
- Add dashed underline (1.3px) to indicate clickable steps with definitions
- Enable definitions mode by default
- Regenerate stepDefinitions.ts with correct patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 18:25:02 +01:00

123 lines
3.5 KiB
TypeScript

// Extract step definitions from feature files and generate a data file with source code
import * as fs from 'fs';
import * as path from 'path';
interface StepDefinition {
pattern: string;
keyword: 'Given' | 'When' | 'Then';
file: string;
sourceCode: string;
lineNumber: number;
}
const stepFiles = [
'features/step_definitions/navigation.steps.ts',
'features/step_definitions/form.steps.ts',
'features/step_definitions/screen.steps.ts',
];
function extractStepDefinitions(): StepDefinition[] {
const definitions: StepDefinition[] = [];
for (const filePath of stepFiles) {
const fullPath = path.join(process.cwd(), filePath);
if (!fs.existsSync(fullPath)) continue;
const content = fs.readFileSync(fullPath, 'utf-8');
const lines = content.split('\n');
const fileName = path.basename(filePath);
// Find step definitions line by line
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Match Given/When/Then at the start of a line
// Handle escaped quotes in patterns (e.g., 'l\'écran contient')
const match = line?.match(/^(Given|When|Then)\s*\(\s*(['"`])((?:[^\\]|\\.)*?)\2/);
if (match && match[3]) {
const keyword = match[1] as 'Given' | 'When' | 'Then';
// Unescape the pattern (remove backslashes before quotes)
const pattern = match[3].replace(/\\(['"`])/g, '$1');
// Extract the full function body
const sourceCode = extractFunctionBody(lines, i);
definitions.push({
pattern,
keyword,
file: fileName,
sourceCode,
lineNumber: i + 1,
});
}
}
}
return definitions;
}
function extractFunctionBody(lines: string[], startLine: number): string {
// Look for the closing }); which marks the end of a step definition
for (let i = startLine; i < lines.length; i++) {
const line = lines[i].trim();
if (line === '});' || line.endsWith('});')) {
const extracted = lines.slice(startLine, i + 1);
return extracted.join('\n');
}
}
// Fallback: return just the start line if we couldn't find the end
return lines[startLine] || '';
}
async function generateStepDefinitionsFile() {
const definitions = extractStepDefinitions();
const findFunctionCode = `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;
}`;
const output = `// 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[] = ${JSON.stringify(definitions, null, 2)};
${findFunctionCode}
`;
await Bun.write('src/data/stepDefinitions.ts', output);
console.log(`Generated ${definitions.length} step definitions`);
}
generateStepDefinitionsFile().catch(console.error);