@@ -27444,12 +27444,9 @@ class LinkGenerator {
2744427444 else if (markdownRelativePath.endsWith('.spec.ts')) {
2744527445 markdownRelativePath = markdownRelativePath.replace(/\.spec\.ts$/, '.md');
2744627446 }
27447- else if (markdownRelativePath.startsWith('test_') && markdownRelativePath.endsWith('.py')) {
27448- // Handle pytest files: test_example.py -> test_example.md
27449- markdownRelativePath = markdownRelativePath.replace(/\.py$/, '.md');
27450- }
27451- else if (markdownRelativePath.endsWith('_test.py')) {
27452- // Handle pytest files: example_test.py -> example_test.md
27447+ else if (markdownRelativePath.endsWith('.py')) {
27448+ // Handle all Python test files: convert .py extension to .md
27449+ // This fixes the issue with files in subfolders not getting .md extension
2745327450 markdownRelativePath = markdownRelativePath.replace(/\.py$/, '.md');
2745427451 }
2745527452 return markdownRelativePath;
@@ -27987,7 +27984,6 @@ class PytestExtractor {
2798727984 const testMatch = trimmedLine.match(/^def\s+(test_\w+)\s*\([^)]*\)\s*:/);
2798827985 if (testMatch) {
2798927986 const testName = testMatch[1];
27990- const fullTestName = currentClass ? `${currentClass}::${testName}` : testName;
2799127987 const describeName = currentClass || path.basename(filePath, '.py');
2799227988 // Look ahead for docstring after the function definition
2799327989 let functionDocstring = [];
@@ -28042,9 +28038,14 @@ class PytestExtractor {
2804228038 const link = this.linkGenerator.generateTestLink(filePath, lineNumber, describeName, testName);
2804328039 // Extract tags from markers and description
2804428040 const tags = [...currentMarkers, ...this.tagProcessor.extractTags(describeName, description)];
28041+ // Format test names: remove "test_" prefix and replace underscores with spaces
28042+ const formattedTestName = this.formatTestName(testName);
28043+ const formattedFullTestName = currentClass
28044+ ? `${currentClass}::${formattedTestName}`
28045+ : formattedTestName;
2804528046 tests.push({
28046- testName: fullTestName ,
28047- shortName: testName ,
28047+ testName: formattedFullTestName ,
28048+ shortName: formattedTestName ,
2804828049 describeName,
2804928050 link,
2805028051 description,
@@ -28054,7 +28055,7 @@ class PytestExtractor {
2805428055 framework: 'pytest'
2805528056 });
2805628057 if (this.verbose) {
28057- console.log(` Found test: ${fullTestName } with markers: [${currentMarkers.join(', ')}]`);
28058+ console.log(` Found test: ${formattedFullTestName } with markers: [${currentMarkers.join(', ')}]`);
2805828059 }
2805928060 // Reset markers and docstring after processing
2806028061 currentMarkers = [];
@@ -28068,6 +28069,19 @@ class PytestExtractor {
2806828069 }
2806928070 return tests;
2807028071 }
28072+ /**
28073+ * Format test name: remove "test_" prefix and replace underscores with spaces
28074+ */
28075+ formatTestName(testName) {
28076+ let formatted = testName;
28077+ // Remove "test_" prefix if present
28078+ if (formatted.startsWith('test_')) {
28079+ formatted = formatted.substring(5);
28080+ }
28081+ // Replace underscores with spaces
28082+ formatted = formatted.replace(/_/g, ' ');
28083+ return formatted;
28084+ }
2807128085 /**
2807228086 * Generate a summary for the file including test type counts
2807328087 */
@@ -28092,25 +28106,30 @@ class PytestExtractor {
2809228106 };
2809328107 }
2809428108 /**
28095- * Parse docstring content for test description and steps
28109+ * Parse docstring content for test description and steps - preserving original line breaks
2809628110 */
2809728111 parseTestDescription(docstringLines) {
2809828112 if (docstringLines.length === 0) {
2809928113 return '';
2810028114 }
2810128115 const parsed = {
28102- description: [],
2810328116 given: '',
2810428117 when: '',
2810528118 then: '',
2810628119 and: [],
2810728120 steps: []
2810828121 };
2810928122 let currentSection = 'description';
28123+ const descriptionLines = [];
2811028124 for (const line of docstringLines) {
2811128125 const trimmed = line.trim();
28112- if (!trimmed)
28126+ if (!trimmed) {
28127+ // Preserve empty lines in description
28128+ if (currentSection === 'description') {
28129+ descriptionLines.push('');
28130+ }
2811328131 continue;
28132+ }
2811428133 // Check for bullet points (steps)
2811528134 if (trimmed.startsWith('* ')) {
2811628135 parsed.steps.push(trimmed.substring(2).trim());
@@ -28138,11 +28157,11 @@ class PytestExtractor {
2813828157 }
2813928158 // Add to current section
2814028159 if (currentSection === 'description') {
28141- parsed.description .push(trimmed);
28160+ descriptionLines .push(trimmed);
2814228161 }
2814328162 }
28144- // Format the description
28145- let result = parsed.description. join(' ').trim();
28163+ // Format the description - preserve line breaks from original docstring
28164+ let result = descriptionLines. join('\n ').trim();
2814628165 // Add BDD sections if present
2814728166 if (parsed.given)
2814828167 result += `\n\n**Given:** ${parsed.given}`;
@@ -28271,9 +28290,9 @@ class MarkdownGenerator {
2827128290 content += '|------|------|-----------|-------------|\n';
2827228291 for (const test of tests) {
2827328292 const typeEmoji = this.getTestTypeEmoji(test.testType);
28274- const testName = this.escapeMarkdown (test.testName);
28293+ const testName = this.escapeMarkdownForTable (test.testName);
2827528294 const link = `[L${test.lineNumber}](${test.link})`;
28276- const description = this.escapeMarkdown (test.description || 'No description available');
28295+ const description = this.escapeMarkdownForTable (test.description || 'No description available');
2827728296 content += `| ${typeEmoji} | ${link} | ${testName} | ${description} |\n`;
2827828297 }
2827928298 content += '\n---\n';
@@ -28373,12 +28392,12 @@ class MarkdownGenerator {
2837328392 content += '| Category | File | Link | Test Name | Description |\n';
2837428393 content += '|----------|------|------|-----------|-------------|\n';
2837528394 for (const test of allTests) {
28376- const category = this.escapeMarkdown (test.category);
28395+ const category = this.escapeMarkdownForTable (test.category);
2837728396 const markdownRelativePath = this.linkGenerator.getMarkdownPath(test.filePath);
2837828397 const fileName = `[${test.fileName}](${markdownRelativePath})`;
28379- const testName = this.escapeMarkdown (test.testName);
28398+ const testName = this.escapeMarkdownForTable (test.testName);
2838028399 const link = `[L${test.lineNumber}](${test.link})`;
28381- const description = this.escapeMarkdown (test.description || 'No description available');
28400+ const description = this.escapeMarkdownForTable (test.description || 'No description available');
2838228401 content += `| ${category} | ${fileName} | ${link} | ${testName} | ${description} |\n`;
2838328402 }
2838428403 // Add tag-based index
@@ -28396,7 +28415,7 @@ class MarkdownGenerator {
2839628415 for (const test of testsWithTag) {
2839728416 const markdownRelativePath = this.linkGenerator.getMarkdownPath(test.filePath);
2839828417 const fileName = `[${test.fileName}](${markdownRelativePath})`;
28399- const testName = this.escapeMarkdown (test.testName);
28418+ const testName = this.escapeMarkdownForTable (test.testName);
2840028419 const link = `[L${test.lineNumber}](${test.link})`;
2840128420 content += `| ${fileName} | ${link} | ${testName} |\n`;
2840228421 }
@@ -28484,6 +28503,15 @@ class MarkdownGenerator {
2848428503 default: return '❓';
2848528504 }
2848628505 }
28506+ /**
28507+ * Escape markdown special characters for table cells - preserves line breaks as <br>
28508+ */
28509+ escapeMarkdownForTable(text) {
28510+ return text
28511+ .replace(/\|/g, '\\|')
28512+ .replace(/\n/g, '<br>')
28513+ .trim();
28514+ }
2848728515 /**
2848828516 * Escape markdown special characters
2848928517 */
0 commit comments