Files
markitect-main/tests/test_issue_138_filename_generation.py
tegwick 312bf8c7bf feat: complete TDD8 implementation of markdown file explosion - Issue #138
Complete implementation of md-explode command for transforming single
markdown files into organized directory structures:

Core Implementation:
- MarkdownSection class for hierarchical document modeling
- extract_headings() - Parse markdown headings with levels
- parse_markdown_structure() - Build section hierarchy from content
- generate_safe_filename() - Convert headings to filesystem-safe names
- explode_markdown_file() - Main explosion functionality
- DirectoryStructureBuilder - Create organized file/directory structures

CLI Integration:
- md-explode command with comprehensive options
- --dry-run for previewing structure
- --verbose for detailed output
- --max-depth for limiting nesting
- --output-dir for custom output location

Key Features:
- Hierarchical structure preservation (# → ## → ###)
- Smart filename generation with Unicode support
- Front matter handling and preservation
- Content integrity maintenance
- Cross-platform filesystem compatibility
- Comprehensive error handling and validation

Refactoring Applied:
- Eliminated code duplication between filename functions
- Extracted front matter processing into dedicated function
- Modularized CLI command with helper functions
- Improved error handling and user feedback

Documentation:
- Complete API documentation with docstrings
- Comprehensive user documentation (docs/md-explode-command.md)
- Usage examples and troubleshooting guide
- Integration instructions with other MarkiTect commands

Testing: 47 comprehensive tests covering all functionality
Status: Production-ready, full TDD8 cycle completed
Performance: Efficient for documents with thousands of sections

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-07 15:44:30 +02:00

214 lines
8.0 KiB
Python

"""
Test filename generation functionality for Issue #138: Explode Markdown file to markdown directory.
This test module covers the conversion of markdown headings to filesystem-safe filenames,
including special character handling, deduplication, and cross-platform compatibility.
"""
import pytest
from pathlib import Path
# Import will fail initially (RED phase) until implementation exists
try:
from markitect.plugins.builtin.markdown_commands import (
generate_safe_filename,
sanitize_heading_text,
resolve_filename_conflicts,
FilenameGenerator
)
except ImportError:
# Expected during RED phase - tests should fail initially
generate_safe_filename = None
sanitize_heading_text = None
resolve_filename_conflicts = None
FilenameGenerator = None
class TestFilenameGeneration:
"""Test conversion of headings to filesystem-safe filenames."""
def test_generate_safe_filename_basic(self):
"""Test basic filename generation from simple headings."""
# This should fail initially (RED phase)
# Simple text
assert generate_safe_filename("Chapter 1") == "chapter_1"
# Text with multiple spaces
assert generate_safe_filename("Chapter 1 Introduction") == "chapter_1_introduction"
# Text with leading/trailing whitespace
assert generate_safe_filename(" Chapter 1 ") == "chapter_1"
def test_generate_safe_filename_special_characters(self):
"""Test filename generation with special characters."""
# This should fail initially (RED phase)
# Common special characters
assert generate_safe_filename("Chapter 1: Getting Started!") == "chapter_1_getting_started"
# Punctuation and symbols
assert generate_safe_filename("What's New? (Version 2.0)") == "whats_new_version_2_0"
# Path-like characters
assert generate_safe_filename("File/Path\\Issues") == "file_path_issues"
# Unicode characters
assert generate_safe_filename("Café & Résumé") == "cafe_resume"
def test_generate_safe_filename_length_limits(self):
"""Test filename generation with very long headings."""
# This should fail initially (RED phase)
long_heading = "This is a very long chapter title that exceeds normal filename length limits and should be truncated appropriately while preserving meaning"
filename = generate_safe_filename(long_heading)
# Should be truncated but still meaningful
assert len(filename) <= 100 # Reasonable limit
assert filename.startswith("this_is_a_very_long_chapter")
assert not filename.endswith("_") # No trailing underscore
def test_generate_safe_filename_edge_cases(self):
"""Test filename generation for edge cases."""
# This should fail initially (RED phase)
# Empty or whitespace-only
assert generate_safe_filename("") == "untitled"
assert generate_safe_filename(" ") == "untitled"
# Only special characters
assert generate_safe_filename("!!!???") == "untitled"
# Numbers only
assert generate_safe_filename("123") == "123"
# Single character
assert generate_safe_filename("A") == "a"
def test_sanitize_heading_text(self):
"""Test text sanitization before filename conversion."""
# This should fail initially (RED phase)
# Remove markdown formatting
assert sanitize_heading_text("**Bold Text**") == "Bold Text"
assert sanitize_heading_text("*Italic Text*") == "Italic Text"
assert sanitize_heading_text("`Code Text`") == "Code Text"
# Remove links
assert sanitize_heading_text("[Link Text](url)") == "Link Text"
assert sanitize_heading_text("Text with [link](url) inside") == "Text with link inside"
# Multiple formatting
assert sanitize_heading_text("**Bold** and *italic* and `code`") == "Bold and italic and code"
def test_resolve_filename_conflicts(self):
"""Test resolution of duplicate filenames."""
# This should fail initially (RED phase)
existing_files = ["chapter_1.md", "introduction.md"]
# No conflict
assert resolve_filename_conflicts("chapter_2", existing_files) == "chapter_2"
# Conflict - should append number
assert resolve_filename_conflicts("chapter_1", existing_files) == "chapter_1_2"
# Multiple conflicts
existing_with_duplicates = ["chapter_1.md", "chapter_1_2.md", "chapter_1_3.md"]
assert resolve_filename_conflicts("chapter_1", existing_with_duplicates) == "chapter_1_4"
class TestFilenameGenerator:
"""Test the FilenameGenerator class for managing filename generation across a project."""
def test_filename_generator_initialization(self):
"""Test FilenameGenerator initialization and configuration."""
# This should fail initially (RED phase)
generator = FilenameGenerator(
max_length=50,
separator="_",
case_style="lower"
)
assert generator.max_length == 50
assert generator.separator == "_"
assert generator.case_style == "lower"
def test_filename_generator_generate_unique(self):
"""Test generating unique filenames with conflict tracking."""
# This should fail initially (RED phase)
generator = FilenameGenerator()
# First occurrence
filename1 = generator.generate("Chapter 1")
assert filename1 == "chapter_1"
# Duplicate should get suffix
filename2 = generator.generate("Chapter 1")
assert filename2 == "chapter_1_2"
# Third occurrence
filename3 = generator.generate("Chapter 1")
assert filename3 == "chapter_1_3"
def test_filename_generator_numbering_preservation(self):
"""Test that numbered headings maintain their order."""
# This should fail initially (RED phase)
generator = FilenameGenerator(preserve_numbers=True)
assert generator.generate("1. Introduction") == "01_introduction"
assert generator.generate("2. Getting Started") == "02_getting_started"
assert generator.generate("10. Advanced Topics") == "10_advanced_topics"
def test_filename_generator_different_separators(self):
"""Test filename generation with different separator styles."""
# This should fail initially (RED phase)
# Underscore separator (default)
generator_underscore = FilenameGenerator(separator="_")
assert generator_underscore.generate("Chapter One") == "chapter_one"
# Hyphen separator
generator_hyphen = FilenameGenerator(separator="-")
assert generator_hyphen.generate("Chapter One") == "chapter-one"
# No separator (camelCase style)
generator_camel = FilenameGenerator(separator="", case_style="camel")
assert generator_camel.generate("Chapter One") == "chapterOne"
def test_filename_generator_case_styles(self):
"""Test different case style options."""
# This should fail initially (RED phase)
# Lower case (default)
generator_lower = FilenameGenerator(case_style="lower")
assert generator_lower.generate("Chapter One") == "chapter_one"
# Upper case
generator_upper = FilenameGenerator(case_style="upper")
assert generator_upper.generate("Chapter One") == "CHAPTER_ONE"
# Title case
generator_title = FilenameGenerator(case_style="title")
assert generator_title.generate("Chapter One") == "Chapter_One"
def test_filename_generator_reset(self):
"""Test resetting the filename generator state."""
# This should fail initially (RED phase)
generator = FilenameGenerator()
# Generate some duplicates
generator.generate("Chapter 1") # chapter_1
generator.generate("Chapter 1") # chapter_1_2
# Reset should clear the tracking
generator.reset()
# Should start over
filename = generator.generate("Chapter 1")
assert filename == "chapter_1"