""" 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"