diff --git a/.coverage b/.coverage index 836b9d2..e8a719a 100644 Binary files a/.coverage and b/.coverage differ diff --git a/tests/rules/test_block_rules.py b/tests/rules/test_block_rules.py index adde78e..52e5c20 100644 --- a/tests/rules/test_block_rules.py +++ b/tests/rules/test_block_rules.py @@ -1,13 +1,32 @@ -# test_block_rules.py - Tests for block rules +# test_block_rules.py - Tests for block rules in BDD style +""" +Tests for all block-related rules including: +- UnterminatedBlockRule: Ensures that all blocks are properly terminated +- BlockSpacingRule: Validates proper spacing around blocks +""" import unittest from asciidoc_linter.rules.block_rules import UnterminatedBlockRule, BlockSpacingRule class TestUnterminatedBlockRule(unittest.TestCase): + """Tests for UnterminatedBlockRule. + This rule ensures that all blocks (like listing blocks, example blocks, etc.) + are properly terminated with their respective end markers. + """ + def setUp(self): + """ + Given an UnterminatedBlockRule instance + """ self.rule = UnterminatedBlockRule() def test_terminated_block(self): + """ + Given a document with a properly terminated block + When the unterminated block rule is checked + Then no findings should be reported + """ + # Given: A document with a properly terminated block content = [ "Some text", "----", @@ -16,12 +35,26 @@ def test_terminated_block(self): "----", "More text" ] + + # When: We check each line for unterminated blocks findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 0) + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Properly terminated block should not produce findings" + ) def test_unterminated_block(self): + """ + Given a document with an unterminated block + When the unterminated block rule is checked + Then one finding should be reported + And the finding should point to the block start line + """ + # Given: A document with an unterminated block content = [ "Some text", "----", @@ -29,13 +62,31 @@ def test_unterminated_block(self): "with no end marker", "More text" ] + + # When: We check each line for unterminated blocks findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 1) - self.assertEqual(findings[0].line_number, 2) + + # Then: One finding should be reported + self.assertEqual( + len(findings), 1, + "Unterminated block should produce one finding" + ) + + # And: The finding should point to the block start line + self.assertEqual( + findings[0].line_number, 2, + "Finding should point to the line where the block starts" + ) def test_multiple_blocks(self): + """ + Given a document with multiple blocks + When the unterminated block rule is checked + Then only unterminated blocks should be reported + """ + # Given: A document with multiple blocks, one unterminated content = [ "====", "Example block", @@ -48,16 +99,37 @@ def test_multiple_blocks(self): "Sidebar content", "****" ] + + # When: We check each line for unterminated blocks findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 1) + + # Then: Only one finding should be reported + self.assertEqual( + len(findings), 1, + "Only the unterminated listing block should be reported" + ) class TestBlockSpacingRule(unittest.TestCase): + """Tests for BlockSpacingRule. + This rule ensures that blocks have proper spacing before and after them, + with some exceptions for headings. + """ + def setUp(self): + """ + Given a BlockSpacingRule instance + """ self.rule = BlockSpacingRule() def test_correct_spacing(self): + """ + Given a document with correct block spacing + When the block spacing rule is checked + Then no findings should be reported + """ + # Given: A document with proper spacing around blocks content = [ "Some text", "", @@ -67,12 +139,26 @@ def test_correct_spacing(self): "", "More text" ] + + # When: We check each line for spacing issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 0) + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Properly spaced block should not produce findings" + ) def test_missing_space_before(self): + """ + Given a document with missing space before a block + When the block spacing rule is checked + Then one finding should be reported + And the finding should mention missing preceding space + """ + # Given: A document with missing space before block content = [ "Some text", "----", @@ -81,13 +167,32 @@ def test_missing_space_before(self): "", "More text" ] + + # When: We check each line for spacing issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 1) - self.assertTrue("preceded by" in findings[0].message) + + # Then: One finding should be reported + self.assertEqual( + len(findings), 1, + "Missing space before block should produce one finding" + ) + + # And: The finding should mention missing preceding space + self.assertTrue( + "preceded by" in findings[0].message, + "Finding should mention missing preceding space" + ) def test_missing_space_after(self): + """ + Given a document with missing space after a block + When the block spacing rule is checked + Then one finding should be reported + And the finding should mention missing following space + """ + # Given: A document with missing space after block content = [ "Some text", "", @@ -96,13 +201,32 @@ def test_missing_space_after(self): "----", "More text" ] + + # When: We check each line for spacing issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 1) - self.assertTrue("followed by" in findings[0].message) + + # Then: One finding should be reported + self.assertEqual( + len(findings), 1, + "Missing space after block should produce one finding" + ) + + # And: The finding should mention missing following space + self.assertTrue( + "followed by" in findings[0].message, + "Finding should mention missing following space" + ) def test_heading_exception(self): + """ + Given a document with blocks adjacent to headings + When the block spacing rule is checked + Then no findings should be reported + Because headings are an exception to the spacing rule + """ + # Given: A document with blocks adjacent to headings content = [ "= Heading", "----", @@ -110,10 +234,17 @@ def test_heading_exception(self): "----", "= Another Heading" ] + + # When: We check each line for spacing issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 0) + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Blocks adjacent to headings should not produce findings" + ) if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/rules/test_block_rules.py.meta b/tests/rules/test_block_rules.py.meta index 0f96998..15ff83e 100644 --- a/tests/rules/test_block_rules.py.meta +++ b/tests/rules/test_block_rules.py.meta @@ -1 +1 @@ -Tests for block rules \ No newline at end of file +Tests for block rules in BDD style \ No newline at end of file diff --git a/tests/rules/test_heading_rules.py b/tests/rules/test_heading_rules.py index e84fb62..f6a63b3 100755 --- a/tests/rules/test_heading_rules.py +++ b/tests/rules/test_heading_rules.py @@ -1,70 +1,111 @@ -# test_heading_rules.py - Tests for heading rules +# test_heading_rules.py - Tests for heading rules in BDD style """ Tests for all heading-related rules including: -- HeadingHierarchyRule (HEAD001) -- HeadingFormatRule (HEAD002) -- HeadingMultipleTopLevelRule (HEAD003) +- HeadingHierarchyRule (HEAD001): Ensures headings follow proper hierarchy +- HeadingFormatRule (HEAD002): Validates heading format conventions +- HeadingMultipleTopLevelRule (HEAD003): Checks for multiple top-level headings """ import unittest from asciidoc_linter.rules import Finding, Severity, Position -from asciidoc_linter.rules.heading_rules import HeadingHierarchyRule, HeadingFormatRule, MultipleTopLevelHeadingsRule +from asciidoc_linter.rules.heading_rules import ( + HeadingHierarchyRule, + HeadingFormatRule, + MultipleTopLevelHeadingsRule +) class TestHeadingFormatRule(unittest.TestCase): - """Tests for HEAD002: Heading Format Rule""" + """Tests for HEAD002: Heading Format Rule. + This rule ensures that headings follow proper formatting conventions: + - Space after heading markers (=, ==, etc.) + - Proper capitalization + """ def setUp(self): + """ + Given a HeadingFormatRule instance + """ self.rule = HeadingFormatRule() def test_valid_format(self): - """Test that valid heading formats produce no findings""" + """ + Given a document with properly formatted headings + When the heading format rule is checked + Then no findings should be reported + """ + # Given: A document with properly formatted headings content = """ = Level 1 == Level 2 """ + # When: We check the heading format findings = self.rule.check(content) - self.assertEqual(len(findings), 0) + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Properly formatted headings should not produce findings" + ) def test_invalid_format(self): - """Test detection of invalid heading formats""" + """ + Given a document with improperly formatted headings + When the heading format rule is checked + Then four findings should be reported + And two findings should be about missing spaces + And two findings should be about improper capitalization + """ + # Given: A document with formatting issues content = """=level 1 ==level 2""" + + # When: We check the heading format findings = self.rule.check(content) - # Debug output - print("\nExpected findings:") - print("1. No space after = in line 1") - print("2. Lowercase in line 1 ('level')") - print("3. No space after == in line 2") - print("4. Lowercase in line 2 ('level')") - - print("\nActual findings:") - for i, f in enumerate(findings, 1): - print(f"{i}. {f.message} (line {f.position.line}): {f.context}") - - # We expect 4 findings: - # 1. No space after = in line 1 - # 2. Lowercase in line 1 ('level') - # 3. No space after == in line 2 - # 4. Lowercase in line 2 ('level') - self.assertEqual(len(findings), 4) - self.assertTrue(all(f.rule_id == "HEAD002" for f in findings)) - - # Verify specific findings + # Then: We should have exactly four findings + self.assertEqual( + len(findings), 4, + "Expected four findings for formatting issues" + ) + + # And: All findings should have the correct rule ID + self.assertTrue( + all(f.rule_id == "HEAD002" for f in findings), + "All findings should be from HEAD002 rule" + ) + + # And: We should have two space-related findings space_findings = [f for f in findings if "Missing space" in f.message] - case_findings = [f for f in findings if "uppercase" in f.message] + self.assertEqual( + len(space_findings), 2, + "Should have two 'missing space' findings" + ) - self.assertEqual(len(space_findings), 2, "Should have two 'missing space' findings") - self.assertEqual(len(case_findings), 2, "Should have two 'uppercase' findings") + # And: We should have two capitalization-related findings + case_findings = [f for f in findings if "uppercase" in f.message] + self.assertEqual( + len(case_findings), 2, + "Should have two 'uppercase' findings" + ) class TestHeadingHierarchyRule(unittest.TestCase): - """Tests for HEAD001: Heading Hierarchy Rule""" + """Tests for HEAD001: Heading Hierarchy Rule. + This rule ensures that heading levels are properly nested without skipping levels. + """ def setUp(self): + """ + Given a HeadingHierarchyRule instance + """ self.rule = HeadingHierarchyRule() def test_valid_heading_sequence(self): - """Test that valid heading sequences produce no findings""" + """ + Given a document with properly nested heading levels + When the heading hierarchy rule is checked + Then no findings should be reported + """ + # Given: A document with proper heading hierarchy content = """ = Level 1 Some content @@ -75,11 +116,23 @@ def test_valid_heading_sequence(self): === Level 3 Even more content """ + # When: We check the heading hierarchy findings = self.rule.check(content) - self.assertEqual(len(findings), 0, "Valid heading sequence should not produce findings") + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Valid heading sequence should not produce findings" + ) def test_skipped_heading_level(self): - """Test that skipped heading levels are detected""" + """ + Given a document with a skipped heading level + When the heading hierarchy rule is checked + Then one error should be reported + And the error should mention skipped levels + """ + # Given: A document with a skipped heading level content = """ = Level 1 Some content @@ -87,13 +140,36 @@ def test_skipped_heading_level(self): === Level 3 Skipped level 2! """ + # When: We check the heading hierarchy findings = self.rule.check(content) - self.assertEqual(len(findings), 1, "Skipped heading level should produce one finding") - self.assertEqual(findings[0].severity, Severity.ERROR) - self.assertTrue("skipped" in findings[0].message.lower()) + + # Then: We should get exactly one error + self.assertEqual( + len(findings), 1, + "Expected one finding for skipped heading level" + ) + + # And: It should be an error severity + self.assertEqual( + findings[0].severity, + Severity.ERROR, + "Skipped heading level should be reported as error" + ) + + # And: The message should mention skipped levels + self.assertTrue( + "skipped" in findings[0].message.lower(), + "Error message should mention skipped levels" + ) def test_multiple_skipped_levels(self): - """Test that multiple skipped levels are detected""" + """ + Given a document with multiple skipped heading levels + When the heading hierarchy rule is checked + Then one error should be reported + And the error should be of severity ERROR + """ + # Given: A document with multiple skipped levels content = """ = Level 1 Some content @@ -101,28 +177,69 @@ def test_multiple_skipped_levels(self): ==== Level 4 Skipped two levels! """ + # When: We check the heading hierarchy findings = self.rule.check(content) - self.assertEqual(len(findings), 1, "Skipped heading levels should produce one finding") - self.assertEqual(findings[0].severity, Severity.ERROR) + + # Then: We should get exactly one error + self.assertEqual( + len(findings), 1, + "Expected one finding for multiple skipped heading levels" + ) + + # And: It should be an error severity + self.assertEqual( + findings[0].severity, + Severity.ERROR, + "Multiple skipped heading levels should be reported as error" + ) def test_empty_document(self): - """Test handling of empty documents""" + """ + Given an empty document + When the heading hierarchy rule is checked + Then no findings should be reported + """ + # Given: An empty document content = "" + + # When: We check the heading hierarchy findings = self.rule.check(content) - self.assertEqual(len(findings), 0, "Empty document should not produce findings") + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Empty document should not produce findings" + ) def test_no_headings(self): - """Test handling of documents without headings""" + """ + Given a document without any headings + When the heading hierarchy rule is checked + Then no findings should be reported + """ + # Given: A document with no headings content = """ This is a document with no headings just regular text """ + # When: We check the heading hierarchy findings = self.rule.check(content) - self.assertEqual(len(findings), 0, "Document without headings should not produce findings") + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Document without headings should not produce findings" + ) def test_heading_underline_not_counted(self): - """Test that heading underlines are not counted as headings""" + """ + Given a document with heading underlines + When the heading hierarchy rule is checked + Then no findings should be reported + And underlines should not be counted as headings + """ + # Given: A document with heading underlines content = """ Level 1 ======= @@ -132,36 +249,76 @@ def test_heading_underline_not_counted(self): ------- More content """ + # When: We check the heading hierarchy findings = self.rule.check(content) - self.assertEqual(len(findings), 0, "Heading underlines should not be counted as headings") + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Heading underlines should not be counted as headings" + ) class TestMultipleTopLevelHeadingsRule(unittest.TestCase): - """Tests for HEAD003: Multiple Top-Level Headings Rule""" + """Tests for HEAD003: Multiple Top-Level Headings Rule. + This rule ensures that a document has only one top-level heading. + """ def setUp(self): + """ + Given a MultipleTopLevelHeadingsRule instance + """ self.rule = MultipleTopLevelHeadingsRule() def test_single_top_level(self): - """Test that single top-level heading produces no findings""" + """ + Given a document with a single top-level heading + When the multiple top-level headings rule is checked + Then no findings should be reported + """ + # Given: A document with one top-level heading content = """ = Document Title == Section 1 == Section 2 """ + # When: We check for multiple top-level headings findings = self.rule.check(content) - self.assertEqual(len(findings), 0) + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Single top-level heading should not produce findings" + ) def test_multiple_top_level(self): - """Test detection of multiple top-level headings""" + """ + Given a document with multiple top-level headings + When the multiple top-level headings rule is checked + Then one error should be reported + And the error should be of severity ERROR + """ + # Given: A document with multiple top-level headings content = """ = First Title == Section 1 = Second Title == Section 2 """ + # When: We check for multiple top-level headings findings = self.rule.check(content) - self.assertEqual(len(findings), 1) - self.assertEqual(findings[0].severity, Severity.ERROR) + + # Then: We should get exactly one error + self.assertEqual( + len(findings), 1, + "Expected one finding for multiple top-level headings" + ) + + # And: It should be an error severity + self.assertEqual( + findings[0].severity, + Severity.ERROR, + "Multiple top-level headings should be reported as error" + ) if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/rules/test_heading_rules.py.meta b/tests/rules/test_heading_rules.py.meta index d052e7f..d772fe1 100755 --- a/tests/rules/test_heading_rules.py.meta +++ b/tests/rules/test_heading_rules.py.meta @@ -1 +1 @@ -Tests for heading rules with corrected imports and class names \ No newline at end of file +Tests for heading rules in BDD style \ No newline at end of file diff --git a/tests/rules/test_image_rules.py b/tests/rules/test_image_rules.py index 6cacf9f..abe20c1 100644 --- a/tests/rules/test_image_rules.py +++ b/tests/rules/test_image_rules.py @@ -1,101 +1,246 @@ -# test_image_rules.py - Tests for image rules +# test_image_rules.py - Tests for image rules in BDD style +""" +Tests for all image-related rules including: +- ImageAttributesRule: Validates image attributes and file existence + - Checks for alt text presence and quality + - Validates image file existence + - Checks for required attributes in block images + - Handles external URLs differently +""" import unittest from pathlib import Path from asciidoc_linter.rules.image_rules import ImageAttributesRule class TestImageAttributesRule(unittest.TestCase): + """Tests for ImageAttributesRule. + This rule ensures that images have proper attributes and exist in the filesystem. + """ + def setUp(self): + """ + Given an ImageAttributesRule instance + """ self.rule = ImageAttributesRule() def test_inline_image_without_alt(self): + """ + Given a document with an inline image without alt text + When the image attributes rule is checked + Then two findings should be reported + And one finding should be about missing alt text + And one finding should be about the missing file + """ + # Given: A document with an inline image without alt text content = [ "Here is an image:test.png[] without alt text." ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 2) # Missing alt text and file not found - self.assertTrue(any("Missing alt text" in f.message for f in findings)) + + # Then: Two findings should be reported + self.assertEqual( + len(findings), 2, + "Should report both missing alt text and file not found" + ) + + # And: One finding should be about missing alt text + self.assertTrue( + any("Missing alt text" in f.message for f in findings), + "Should report missing alt text" + ) def test_inline_image_with_alt(self): + """ + Given a document with an inline image with alt text + When the image attributes rule is checked + Then only one finding should be reported + And the finding should be about the missing file + """ + # Given: A document with an inline image with alt text content = [ "Here is an image:test.png[A good description of the image] with alt text." ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 1) # Only file not found + + # Then: Only one finding should be reported + self.assertEqual( + len(findings), 1, + "Should only report file not found" + ) def test_block_image_complete(self): + """ + Given a document with a complete block image + When the image attributes rule is checked + Then only one finding should be reported + And the finding should be about the missing file + """ + # Given: A document with a complete block image content = [ "image::test.png[Alt text for image, title=Image Title, width=500]" ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 1) # Only file not found + + # Then: Only one finding should be reported + self.assertEqual( + len(findings), 1, + "Should only report file not found for complete block image" + ) def test_block_image_missing_attributes(self): + """ + Given a document with a block image missing attributes + When the image attributes rule is checked + Then three findings should be reported + And findings should include missing alt text, title, size, and file + """ + # Given: A document with a block image missing attributes content = [ "image::test.png[]" ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 3) # Missing alt, title, size + file not found + + # Then: Three findings should be reported + self.assertEqual( + len(findings), 3, + "Should report missing alt, title, size and file not found" + ) def test_short_alt_text(self): + """ + Given a document with an image having too short alt text + When the image attributes rule is checked + Then a finding about short alt text should be reported + """ + # Given: A document with an image having short alt text content = [ "image:test.png[img]" ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertTrue(any("Alt text too short" in f.message for f in findings)) + + # Then: A finding about short alt text should be reported + self.assertTrue( + any("Alt text too short" in f.message for f in findings), + "Should report alt text being too short" + ) def test_external_url(self): + """ + Given a document with an external image URL + When the image attributes rule is checked + Then no findings should be reported + Because external URLs are not checked for existence + """ + # Given: A document with an external image URL content = [ "image:https://example.com/test.png[External image]" ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 0) # External URLs are not checked for existence + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "External URLs should not be checked for existence" + ) def test_multiple_images_per_line(self): + """ + Given a document with multiple images in one line + When the image attributes rule is checked + Then appropriate findings should be reported for each image + """ + # Given: A document with multiple images in one line content = [ "Here are two images: image:test1.png[] and image:test2.png[Good alt text]" ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 3) # First image: missing alt + not found, Second image: not found + + # Then: Three findings should be reported + self.assertEqual( + len(findings), 3, + "Should report missing alt + not found for first image and not found for second" + ) def test_attribute_parsing(self): + """ + Given a document with complex image attributes + When the image attributes rule is checked + Then only the missing file should be reported + And complex attributes should be parsed correctly + """ + # Given: A document with complex image attributes content = [ 'image::test.png[Alt text, title="Complex, title with, commas", width=500]' ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 1) # Only file not found + + # Then: Only one finding should be reported + self.assertEqual( + len(findings), 1, + "Should only report file not found for image with complex attributes" + ) def test_valid_local_image(self): - # Create a temporary test image + """ + Given a document with a reference to an existing local image + And the image file exists + When the image attributes rule is checked + Then no findings should be reported + """ + # Given: A temporary test image file test_image = Path("test_image.png") test_image.touch() try: + # And: A document referencing the existing image content = [ 'image::test_image.png[Valid test image, title="Test Image", width=500]' ] + + # When: We check the line for image issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 0) # All valid + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Valid local image with proper attributes should not produce findings" + ) finally: - # Clean up + # Clean up: Remove the temporary test image test_image.unlink() if __name__ == '__main__': diff --git a/tests/rules/test_image_rules.py.meta b/tests/rules/test_image_rules.py.meta index 1f02a7a..3e1afd8 100644 --- a/tests/rules/test_image_rules.py.meta +++ b/tests/rules/test_image_rules.py.meta @@ -1 +1 @@ -Tests for image rules \ No newline at end of file +Tests for image rules in BDD style \ No newline at end of file diff --git a/tests/rules/test_whitespace_rules.py b/tests/rules/test_whitespace_rules.py index 38f1206..44f6b5b 100644 --- a/tests/rules/test_whitespace_rules.py +++ b/tests/rules/test_whitespace_rules.py @@ -1,13 +1,37 @@ -# test_whitespace_rules.py - Tests for whitespace rules +# test_whitespace_rules.py - Tests for whitespace rules in BDD style +""" +Tests for whitespace-related rules including: +- Multiple consecutive empty lines +- List marker spacing +- Admonition block spacing +- Trailing whitespace +- Tab usage +- Section title spacing +""" import unittest from asciidoc_linter.rules.whitespace_rules import WhitespaceRule class TestWhitespaceRule(unittest.TestCase): + """Tests for WhitespaceRule. + This rule ensures proper whitespace usage throughout the document, + including line spacing, indentation, and formatting conventions. + """ + def setUp(self): + """ + Given a WhitespaceRule instance + """ self.rule = WhitespaceRule() def test_multiple_empty_lines(self): + """ + Given a document with multiple consecutive empty lines + When the whitespace rule is checked + Then one finding should be reported + And the finding should mention consecutive empty lines + """ + # Given: A document with multiple consecutive empty lines content = [ "First line", "", @@ -15,13 +39,32 @@ def test_multiple_empty_lines(self): "", "Last line" ] + + # When: We check each line for whitespace issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 1) - self.assertTrue("consecutive empty line" in findings[0].message) + + # Then: One finding should be reported + self.assertEqual( + len(findings), 1, + "Multiple consecutive empty lines should produce one finding" + ) + + # And: The finding should mention consecutive empty lines + self.assertTrue( + "consecutive empty line" in findings[0].message, + "Finding should mention consecutive empty lines" + ) def test_list_marker_spacing(self): + """ + Given a document with both valid and invalid list markers + When the whitespace rule is checked + Then three findings should be reported + And each finding should mention space after the marker + """ + # Given: A document with various list markers content = [ "* Valid item", "*Invalid item", @@ -30,14 +73,33 @@ def test_list_marker_spacing(self): ". Valid item", ".Invalid item" ] + + # When: We check each line for whitespace issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 3) + + # Then: Three findings should be reported + self.assertEqual( + len(findings), 3, + "Three invalid list markers should produce three findings" + ) + + # And: Each finding should mention space after the marker for finding in findings: - self.assertTrue("space after the marker" in finding.message) + self.assertTrue( + "space after the marker" in finding.message, + "Finding should mention missing space after marker" + ) def test_admonition_block_spacing(self): + """ + Given a document with admonition blocks + When the whitespace rule is checked + Then findings should be reported for blocks without proper spacing + And findings should mention blank line requirements + """ + # Given: A document with various admonition blocks content = [ "Some text", "NOTE: This needs a blank line", @@ -46,42 +108,99 @@ def test_admonition_block_spacing(self): "More text", "WARNING: This needs a blank line" ] + + # When: We check each line for whitespace issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 2) + + # Then: Two findings should be reported + self.assertEqual( + len(findings), 2, + "Two admonition blocks without proper spacing should produce two findings" + ) + + # And: Each finding should mention blank line requirements for finding in findings: - self.assertTrue("preceded by a blank line" in finding.message) + self.assertTrue( + "preceded by a blank line" in finding.message, + "Finding should mention missing blank line requirement" + ) def test_trailing_whitespace(self): + """ + Given a document with lines containing trailing whitespace + When the whitespace rule is checked + Then findings should be reported for lines with trailing spaces + And findings should mention trailing whitespace + """ + # Given: A document with trailing whitespace content = [ "Line without trailing space", "Line with trailing space ", "Another clean line", "More trailing space " ] + + # When: We check each line for whitespace issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 2) + + # Then: Two findings should be reported + self.assertEqual( + len(findings), 2, + "Two lines with trailing whitespace should produce two findings" + ) + + # And: Each finding should mention trailing whitespace for finding in findings: - self.assertTrue("trailing whitespace" in finding.message) + self.assertTrue( + "trailing whitespace" in finding.message, + "Finding should mention trailing whitespace" + ) def test_tabs(self): + """ + Given a document with lines containing tabs + When the whitespace rule is checked + Then findings should be reported for lines with tabs + And findings should mention tab usage + """ + # Given: A document with tab characters content = [ "Normal line", "\tLine with tab", " Spaces are fine", "\tAnother tab" ] + + # When: We check each line for whitespace issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 2) + + # Then: Two findings should be reported + self.assertEqual( + len(findings), 2, + "Two lines with tabs should produce two findings" + ) + + # And: Each finding should mention tab usage for finding in findings: - self.assertTrue("contains tabs" in finding.message) + self.assertTrue( + "contains tabs" in finding.message, + "Finding should mention tab usage" + ) def test_section_title_spacing(self): + """ + Given a document with section titles + When the whitespace rule is checked + Then findings should be reported for improper spacing around titles + And findings should mention both preceding and following space requirements + """ + # Given: A document with section titles content = [ "Some text", "== Section Title", @@ -91,14 +210,36 @@ def test_section_title_spacing(self): "", "This is correct" ] + + # When: We check each line for whitespace issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 2) - self.assertTrue(any("preceded by" in f.message for f in findings)) - self.assertTrue(any("followed by" in f.message for f in findings)) + + # Then: Two findings should be reported + self.assertEqual( + len(findings), 2, + "Two spacing issues around section titles should produce two findings" + ) + + # And: Findings should mention both preceding and following space requirements + self.assertTrue( + any("preceded by" in f.message for f in findings), + "Should report missing preceding space" + ) + self.assertTrue( + any("followed by" in f.message for f in findings), + "Should report missing following space" + ) def test_valid_document(self): + """ + Given a well-formatted document + When the whitespace rule is checked + Then no findings should be reported + Because all whitespace conventions are followed + """ + # Given: A well-formatted document content = [ "= Document Title", "", @@ -113,7 +254,17 @@ def test_valid_document(self): "", "Normal paragraph." ] + + # When: We check each line for whitespace issues findings = [] for i, line in enumerate(content): findings.extend(self.rule.check_line(line, i, content)) - self.assertEqual(len(findings), 0) \ No newline at end of file + + # Then: No findings should be reported + self.assertEqual( + len(findings), 0, + "Well-formatted document should not produce any findings" + ) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/rules/test_whitespace_rules.py.meta b/tests/rules/test_whitespace_rules.py.meta index ddba808..8ff0842 100644 --- a/tests/rules/test_whitespace_rules.py.meta +++ b/tests/rules/test_whitespace_rules.py.meta @@ -1 +1 @@ -Tests for whitespace rules \ No newline at end of file +Tests for whitespace rules in BDD style \ No newline at end of file