- Test text rendering with markdown - Test unordered and ordered lists - Test table rendering - Test diagram rendering (success and error cases) - Test diagram with custom options - Test code blocks - Test edge cases (empty content, no type) - Test multiple sections - Test md_table_to_html function - Integration tests for mixed content
378 lines
12 KiB
Python
378 lines
12 KiB
Python
from unittest.mock import Mock, patch, MagicMock
|
|
from django.test import TestCase, override_settings
|
|
from abschnitte.models import AbschnittTyp, Textabschnitt
|
|
from abschnitte.utils import render_textabschnitte, md_table_to_html
|
|
|
|
|
|
class MockTextabschnitt:
|
|
"""Mock object for Textabschnitt (since it's abstract)."""
|
|
def __init__(self, abschnitttyp, inhalt):
|
|
self.abschnitttyp = abschnitttyp
|
|
self.inhalt = inhalt
|
|
|
|
|
|
class RenderTextabschnitteTestCase(TestCase):
|
|
"""Test cases for render_textabschnitte function."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
# Create mock AbschnittTyp objects
|
|
self.typ_text = Mock()
|
|
self.typ_text.abschnitttyp = "text"
|
|
|
|
self.typ_liste_ungeordnet = Mock()
|
|
self.typ_liste_ungeordnet.abschnitttyp = "liste ungeordnet"
|
|
|
|
self.typ_liste_geordnet = Mock()
|
|
self.typ_liste_geordnet.abschnitttyp = "liste geordnet"
|
|
|
|
self.typ_tabelle = Mock()
|
|
self.typ_tabelle.abschnitttyp = "tabelle"
|
|
|
|
self.typ_diagramm = Mock()
|
|
self.typ_diagramm.abschnitttyp = "diagramm"
|
|
|
|
self.typ_code = Mock()
|
|
self.typ_code.abschnitttyp = "code"
|
|
|
|
def test_render_basic_text(self):
|
|
"""Test rendering basic text content."""
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_text,
|
|
inhalt="This is **bold** text with *italic*."
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
self.assertEqual(len(result), 1)
|
|
typ, html = result[0]
|
|
self.assertEqual(typ, "text")
|
|
self.assertIn("<strong>bold</strong>", html)
|
|
self.assertIn("<em>italic</em>", html)
|
|
|
|
def test_render_unordered_list(self):
|
|
"""Test rendering unordered list."""
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_liste_ungeordnet,
|
|
inhalt="Item 1\nItem 2\nItem 3"
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
self.assertEqual(len(result), 1)
|
|
typ, html = result[0]
|
|
self.assertEqual(typ, "liste ungeordnet")
|
|
self.assertIn("<ul>", html)
|
|
self.assertIn("<li>Item 1</li>", html)
|
|
self.assertIn("<li>Item 2</li>", html)
|
|
self.assertIn("<li>Item 3</li>", html)
|
|
|
|
def test_render_ordered_list(self):
|
|
"""Test rendering ordered list."""
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_liste_geordnet,
|
|
inhalt="First item\nSecond item\nThird item"
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
self.assertEqual(len(result), 1)
|
|
typ, html = result[0]
|
|
self.assertEqual(typ, "liste geordnet")
|
|
self.assertIn("<ol>", html)
|
|
self.assertIn("<li>First item</li>", html)
|
|
|
|
def test_render_table(self):
|
|
"""Test rendering markdown table."""
|
|
table_content = """| Header 1 | Header 2 |
|
|
|----------|----------|
|
|
| Cell 1 | Cell 2 |
|
|
| Cell 3 | Cell 4 |"""
|
|
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_tabelle,
|
|
inhalt=table_content
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
self.assertEqual(len(result), 1)
|
|
typ, html = result[0]
|
|
self.assertEqual(typ, "tabelle")
|
|
self.assertIn("<table", html)
|
|
self.assertIn("Header 1", html)
|
|
self.assertIn("Cell 1", html)
|
|
self.assertIn("table-bordered", html)
|
|
|
|
@override_settings(MEDIA_URL='/media/')
|
|
@patch('abschnitte.utils.get_cached_diagram')
|
|
def test_render_diagram_success(self, mock_get_cached):
|
|
"""Test rendering diagram with successful cache."""
|
|
mock_get_cached.return_value = 'diagram_cache/plantuml/abc123.svg'
|
|
|
|
diagram_content = """plantuml
|
|
@startuml
|
|
A -> B
|
|
@enduml"""
|
|
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_diagramm,
|
|
inhalt=diagram_content
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
self.assertEqual(len(result), 1)
|
|
typ, html = result[0]
|
|
self.assertEqual(typ, "diagramm")
|
|
|
|
# Verify diagram cache was called correctly
|
|
mock_get_cached.assert_called_once_with(
|
|
'plantuml',
|
|
'@startuml\nA -> B\n@enduml'
|
|
)
|
|
|
|
# Verify HTML contains correct image tag
|
|
self.assertIn('<img', html)
|
|
self.assertIn('/media/diagram_cache/plantuml/abc123.svg', html)
|
|
self.assertIn('width="100%"', html)
|
|
|
|
@patch('abschnitte.utils.get_cached_diagram')
|
|
def test_render_diagram_with_options(self, mock_get_cached):
|
|
"""Test rendering diagram with custom options."""
|
|
mock_get_cached.return_value = 'diagram_cache/mermaid/def456.svg'
|
|
|
|
diagram_content = """mermaid
|
|
option:width="50%" height="300px"
|
|
graph TD
|
|
A --> B"""
|
|
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_diagramm,
|
|
inhalt=diagram_content
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
typ, html = result[0]
|
|
|
|
# Verify custom options are used
|
|
self.assertIn('width="50%"', html)
|
|
self.assertIn('height="300px"', html)
|
|
|
|
# Verify diagram content doesn't include option line
|
|
call_args = mock_get_cached.call_args[0]
|
|
self.assertNotIn('option:', call_args[1])
|
|
|
|
@patch('abschnitte.utils.get_cached_diagram')
|
|
def test_render_diagram_error(self, mock_get_cached):
|
|
"""Test rendering diagram when caching fails."""
|
|
mock_get_cached.side_effect = Exception("Kroki server unavailable")
|
|
|
|
diagram_content = """plantuml
|
|
@startuml
|
|
A -> B
|
|
@enduml"""
|
|
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_diagramm,
|
|
inhalt=diagram_content
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
typ, html = result[0]
|
|
|
|
# Should show error message
|
|
self.assertIn('Error generating diagram', html)
|
|
self.assertIn('Kroki server unavailable', html)
|
|
self.assertIn('text-danger', html)
|
|
|
|
def test_render_code(self):
|
|
"""Test rendering code block."""
|
|
code_content = """def hello():
|
|
print("Hello, World!")"""
|
|
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_code,
|
|
inhalt=code_content
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
typ, html = result[0]
|
|
self.assertEqual(typ, "code")
|
|
self.assertIn("<pre>", html)
|
|
self.assertIn("<code>", html)
|
|
self.assertIn("def hello():", html)
|
|
|
|
def test_render_empty_content(self):
|
|
"""Test rendering with empty or None content."""
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=self.typ_text,
|
|
inhalt=None
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
self.assertEqual(len(result), 1)
|
|
typ, html = result[0]
|
|
self.assertEqual(typ, "text")
|
|
# Should not crash, should return empty or minimal HTML
|
|
|
|
def test_render_multiple_abschnitte(self):
|
|
"""Test rendering multiple sections."""
|
|
abschnitte = [
|
|
MockTextabschnitt(self.typ_text, "First paragraph"),
|
|
MockTextabschnitt(self.typ_liste_ungeordnet, "Item 1\nItem 2"),
|
|
MockTextabschnitt(self.typ_text, "Second paragraph"),
|
|
]
|
|
|
|
result = render_textabschnitte(abschnitte)
|
|
|
|
self.assertEqual(len(result), 3)
|
|
self.assertEqual(result[0][0], "text")
|
|
self.assertEqual(result[1][0], "liste ungeordnet")
|
|
self.assertEqual(result[2][0], "text")
|
|
|
|
def test_render_no_abschnitttyp(self):
|
|
"""Test rendering when abschnitttyp is None."""
|
|
abschnitt = MockTextabschnitt(
|
|
abschnitttyp=None,
|
|
inhalt="Some content"
|
|
)
|
|
|
|
result = render_textabschnitte([abschnitt])
|
|
|
|
self.assertEqual(len(result), 1)
|
|
typ, html = result[0]
|
|
self.assertEqual(typ, "")
|
|
# Should still render as markdown
|
|
self.assertIn("Some content", html)
|
|
|
|
|
|
class MdTableToHtmlTestCase(TestCase):
|
|
"""Test cases for md_table_to_html function."""
|
|
|
|
def test_basic_table(self):
|
|
"""Test converting basic markdown table to HTML."""
|
|
md = """| Name | Age |
|
|
|------|-----|
|
|
| Alice | 30 |
|
|
| Bob | 25 |"""
|
|
|
|
html = md_table_to_html(md)
|
|
|
|
self.assertIn('<table', html)
|
|
self.assertIn('table-bordered', html)
|
|
self.assertIn('table-hover', html)
|
|
self.assertIn('<thead>', html)
|
|
self.assertIn('<tbody>', html)
|
|
self.assertIn('<th>Name</th>', html)
|
|
self.assertIn('<th>Age</th>', html)
|
|
self.assertIn('<td>Alice</td>', html)
|
|
self.assertIn('<td>30</td>', html)
|
|
|
|
def test_table_with_spaces(self):
|
|
"""Test table with various spacing."""
|
|
md = """| Name | Age |
|
|
|--------|-------|
|
|
| Alice | 30 |
|
|
| Bob | 25 |"""
|
|
|
|
html = md_table_to_html(md)
|
|
|
|
# Spaces should be trimmed
|
|
self.assertIn('<th>Name</th>', html)
|
|
self.assertIn('<th>Age</th>', html)
|
|
self.assertIn('<td>Alice</td>', html)
|
|
|
|
def test_table_no_outer_pipes(self):
|
|
"""Test table without outer pipe characters."""
|
|
md = """Name | Age
|
|
-----|-----
|
|
Alice | 30
|
|
Bob | 25"""
|
|
|
|
html = md_table_to_html(md)
|
|
|
|
self.assertIn('<th>Name</th>', html)
|
|
self.assertIn('<td>Alice</td>', html)
|
|
|
|
def test_table_insufficient_rows(self):
|
|
"""Test error handling for malformed table."""
|
|
md = """| Name |
|
|
|------|"""
|
|
|
|
with self.assertRaises(ValueError) as context:
|
|
md_table_to_html(md)
|
|
|
|
self.assertIn("at least header + separator", str(context.exception))
|
|
|
|
def test_table_with_empty_cells(self):
|
|
"""Test table with empty cells."""
|
|
md = """| Name | Age |
|
|
|------|-----|
|
|
| Alice | |
|
|
| | 25 |"""
|
|
|
|
html = md_table_to_html(md)
|
|
|
|
self.assertIn('<td>Alice</td>', html)
|
|
self.assertIn('<td></td>', html)
|
|
self.assertIn('<td>25</td>', html)
|
|
|
|
def test_table_three_columns(self):
|
|
"""Test table with more columns."""
|
|
md = """| First | Middle | Last |
|
|
|-------|--------|------|
|
|
| John | Q | Doe |"""
|
|
|
|
html = md_table_to_html(md)
|
|
|
|
self.assertIn('<th>First</th>', html)
|
|
self.assertIn('<th>Middle</th>', html)
|
|
self.assertIn('<th>Last</th>', html)
|
|
self.assertIn('<td>John</td>', html)
|
|
self.assertIn('<td>Q</td>', html)
|
|
self.assertIn('<td>Doe</td>', html)
|
|
|
|
|
|
class RenderIntegrationTestCase(TestCase):
|
|
"""Integration tests for rendering pipeline."""
|
|
|
|
@patch('abschnitte.utils.get_cached_diagram')
|
|
def test_mixed_content_rendering(self, mock_get_cached):
|
|
"""Test rendering a mix of different content types."""
|
|
mock_get_cached.return_value = 'diagram_cache/test.svg'
|
|
|
|
# Create various types of content
|
|
typ_text = Mock(abschnitttyp="text")
|
|
typ_liste = Mock(abschnitttyp="liste ungeordnet")
|
|
typ_diagram = Mock(abschnitttyp="diagramm")
|
|
|
|
abschnitte = [
|
|
MockTextabschnitt(typ_text, "Introduction paragraph"),
|
|
MockTextabschnitt(typ_liste, "Point 1\nPoint 2\nPoint 3"),
|
|
MockTextabschnitt(typ_diagram, "plantuml\n@startuml\nA -> B\n@enduml"),
|
|
MockTextabschnitt(typ_text, "Conclusion"),
|
|
]
|
|
|
|
result = render_textabschnitte(abschnitte)
|
|
|
|
# Should have 4 results
|
|
self.assertEqual(len(result), 4)
|
|
|
|
# Verify each type was rendered correctly
|
|
self.assertEqual(result[0][0], "text")
|
|
self.assertIn("Introduction", result[0][1])
|
|
|
|
self.assertEqual(result[1][0], "liste ungeordnet")
|
|
self.assertIn("<ul>", result[1][1])
|
|
|
|
self.assertEqual(result[2][0], "diagramm")
|
|
self.assertIn("<img", result[2][1])
|
|
|
|
self.assertEqual(result[3][0], "text")
|
|
self.assertIn("Conclusion", result[3][1])
|