PlanOpticon

planopticon / tests / test_rendering.py
Source Blame History 255 lines
cd8f2f9… leo 1 """Tests for rendering and export utilities."""
cd8f2f9… leo 2
829e24a… leo 3 from unittest.mock import patch
cd8f2f9… leo 4
cd8f2f9… leo 5 from video_processor.models import (
cd8f2f9… leo 6 ActionItem,
cd8f2f9… leo 7 DiagramResult,
cd8f2f9… leo 8 DiagramType,
cd8f2f9… leo 9 KeyPoint,
cd8f2f9… leo 10 ProcessingStats,
cd8f2f9… leo 11 VideoManifest,
cd8f2f9… leo 12 VideoMetadata,
cd8f2f9… leo 13 )
cd8f2f9… leo 14 from video_processor.utils.rendering import render_mermaid, reproduce_chart
cd8f2f9… leo 15
cd8f2f9… leo 16
cd8f2f9… leo 17 class TestRenderMermaid:
cd8f2f9… leo 18 def test_writes_mermaid_source(self, tmp_path):
cd8f2f9… leo 19 code = "graph LR\n A-->B"
cd8f2f9… leo 20 result = render_mermaid(code, tmp_path, "test_diagram")
cd8f2f9… leo 21 assert "mermaid" in result
cd8f2f9… leo 22 assert result["mermaid"].exists()
cd8f2f9… leo 23 assert result["mermaid"].read_text() == code
cd8f2f9… leo 24
cd8f2f9… leo 25 def test_source_file_named_correctly(self, tmp_path):
cd8f2f9… leo 26 result = render_mermaid("graph TD\n X-->Y", tmp_path, "my_chart")
cd8f2f9… leo 27 assert result["mermaid"].name == "my_chart.mermaid"
cd8f2f9… leo 28
cd8f2f9… leo 29 @patch("video_processor.utils.rendering.mmd", create=True)
cd8f2f9… leo 30 def test_svg_png_on_import_error(self, mock_mmd, tmp_path):
cd8f2f9… leo 31 """When mermaid-py is not installed, only source is written."""
cd8f2f9… leo 32 # Simulate import error by using the real code path
cd8f2f9… leo 33 # (mermaid-py may or may not be installed in test env)
cd8f2f9… leo 34 result = render_mermaid("graph LR\n A-->B", tmp_path, "test")
cd8f2f9… leo 35 # At minimum, mermaid source should always be written
cd8f2f9… leo 36 assert "mermaid" in result
cd8f2f9… leo 37 assert result["mermaid"].exists()
cd8f2f9… leo 38
cd8f2f9… leo 39 def test_creates_output_dir(self, tmp_path):
cd8f2f9… leo 40 nested = tmp_path / "a" / "b"
cd8f2f9… leo 41 result = render_mermaid("graph LR\n A-->B", nested, "test")
cd8f2f9… leo 42 assert nested.exists()
cd8f2f9… leo 43 assert result["mermaid"].exists()
cd8f2f9… leo 44
cd8f2f9… leo 45
cd8f2f9… leo 46 class TestReproduceChart:
cd8f2f9… leo 47 def test_bar_chart(self, tmp_path):
cd8f2f9… leo 48 data = {
cd8f2f9… leo 49 "labels": ["A", "B", "C"],
cd8f2f9… leo 50 "values": [10, 20, 30],
cd8f2f9… leo 51 "chart_type": "bar",
cd8f2f9… leo 52 }
cd8f2f9… leo 53 result = reproduce_chart(data, tmp_path, "test")
cd8f2f9… leo 54 assert "svg" in result
cd8f2f9… leo 55 assert "png" in result
cd8f2f9… leo 56 assert result["svg"].exists()
cd8f2f9… leo 57 assert result["png"].exists()
cd8f2f9… leo 58 assert result["svg"].suffix == ".svg"
cd8f2f9… leo 59 assert result["png"].suffix == ".png"
cd8f2f9… leo 60
cd8f2f9… leo 61 def test_line_chart(self, tmp_path):
cd8f2f9… leo 62 data = {
cd8f2f9… leo 63 "labels": ["Jan", "Feb", "Mar"],
cd8f2f9… leo 64 "values": [5, 15, 10],
cd8f2f9… leo 65 "chart_type": "line",
cd8f2f9… leo 66 }
cd8f2f9… leo 67 result = reproduce_chart(data, tmp_path, "line_test")
cd8f2f9… leo 68 assert "svg" in result
cd8f2f9… leo 69 assert result["svg"].exists()
cd8f2f9… leo 70
cd8f2f9… leo 71 def test_pie_chart(self, tmp_path):
cd8f2f9… leo 72 data = {
cd8f2f9… leo 73 "labels": ["Dogs", "Cats"],
cd8f2f9… leo 74 "values": [60, 40],
cd8f2f9… leo 75 "chart_type": "pie",
cd8f2f9… leo 76 }
cd8f2f9… leo 77 result = reproduce_chart(data, tmp_path, "pie_test")
cd8f2f9… leo 78 assert "svg" in result
cd8f2f9… leo 79
cd8f2f9… leo 80 def test_scatter_chart(self, tmp_path):
cd8f2f9… leo 81 data = {
cd8f2f9… leo 82 "labels": ["X1", "X2", "X3"],
cd8f2f9… leo 83 "values": [1, 4, 9],
cd8f2f9… leo 84 "chart_type": "scatter",
cd8f2f9… leo 85 }
cd8f2f9… leo 86 result = reproduce_chart(data, tmp_path, "scatter_test")
cd8f2f9… leo 87 assert "svg" in result
cd8f2f9… leo 88
cd8f2f9… leo 89 def test_empty_data_returns_empty(self, tmp_path):
cd8f2f9… leo 90 data = {"labels": [], "values": [], "chart_type": "bar"}
cd8f2f9… leo 91 result = reproduce_chart(data, tmp_path, "empty")
cd8f2f9… leo 92 assert result == {}
cd8f2f9… leo 93
cd8f2f9… leo 94 def test_missing_values_returns_empty(self, tmp_path):
cd8f2f9… leo 95 data = {"labels": ["A", "B"]}
cd8f2f9… leo 96 result = reproduce_chart(data, tmp_path, "no_vals")
cd8f2f9… leo 97 assert result == {}
cd8f2f9… leo 98
cd8f2f9… leo 99 def test_creates_output_dir(self, tmp_path):
cd8f2f9… leo 100 nested = tmp_path / "charts" / "output"
cd8f2f9… leo 101 data = {"labels": ["A"], "values": [1], "chart_type": "bar"}
829e24a… leo 102 reproduce_chart(data, nested, "test")
cd8f2f9… leo 103 assert nested.exists()
cd8f2f9… leo 104
cd8f2f9… leo 105
cd8f2f9… leo 106 class TestExportAllFormats:
cd8f2f9… leo 107 def _make_manifest(self) -> VideoManifest:
cd8f2f9… leo 108 return VideoManifest(
cd8f2f9… leo 109 video=VideoMetadata(title="Test Video"),
cd8f2f9… leo 110 stats=ProcessingStats(frames_extracted=5, diagrams_detected=1),
cd8f2f9… leo 111 analysis_md="results/analysis.md",
cd8f2f9… leo 112 key_points=[KeyPoint(point="Important finding")],
cd8f2f9… leo 113 action_items=[ActionItem(action="Follow up", assignee="Alice")],
cd8f2f9… leo 114 diagrams=[
cd8f2f9… leo 115 DiagramResult(
cd8f2f9… leo 116 frame_index=0,
cd8f2f9… leo 117 diagram_type=DiagramType.flowchart,
cd8f2f9… leo 118 confidence=0.9,
cd8f2f9… leo 119 description="Login flow",
cd8f2f9… leo 120 mermaid="graph LR\n Login-->Dashboard",
cd8f2f9… leo 121 image_path="diagrams/diagram_0.jpg",
cd8f2f9… leo 122 ),
cd8f2f9… leo 123 ],
cd8f2f9… leo 124 )
cd8f2f9… leo 125
cd8f2f9… leo 126 def test_export_renders_mermaid(self, tmp_path):
cd8f2f9… leo 127 from video_processor.utils.export import export_all_formats
cd8f2f9… leo 128
cd8f2f9… leo 129 manifest = self._make_manifest()
cd8f2f9… leo 130
cd8f2f9… leo 131 # Create required dirs and files
cd8f2f9… leo 132 (tmp_path / "results").mkdir()
cd8f2f9… leo 133 (tmp_path / "results" / "analysis.md").write_text("# Test\nContent")
cd8f2f9… leo 134 (tmp_path / "diagrams").mkdir()
cd8f2f9… leo 135 (tmp_path / "diagrams" / "diagram_0.jpg").write_bytes(b"\xff\xd8\xff")
cd8f2f9… leo 136
cd8f2f9… leo 137 result = export_all_formats(tmp_path, manifest)
cd8f2f9… leo 138
cd8f2f9… leo 139 # Mermaid source should be written
cd8f2f9… leo 140 assert (tmp_path / "diagrams" / "diagram_0.mermaid").exists()
cd8f2f9… leo 141 # Manifest should be updated
cd8f2f9… leo 142 assert result.diagrams[0].mermaid_path is not None
cd8f2f9… leo 143
cd8f2f9… leo 144 def test_export_generates_html(self, tmp_path):
cd8f2f9… leo 145 from video_processor.utils.export import export_all_formats
cd8f2f9… leo 146
cd8f2f9… leo 147 manifest = self._make_manifest()
cd8f2f9… leo 148 (tmp_path / "results").mkdir()
cd8f2f9… leo 149 (tmp_path / "results" / "analysis.md").write_text("# Test")
cd8f2f9… leo 150 (tmp_path / "diagrams").mkdir()
cd8f2f9… leo 151
cd8f2f9… leo 152 result = export_all_formats(tmp_path, manifest)
cd8f2f9… leo 153 assert result.analysis_html is not None
cd8f2f9… leo 154 html_path = tmp_path / result.analysis_html
cd8f2f9… leo 155 assert html_path.exists()
cd8f2f9… leo 156 html_content = html_path.read_text()
cd8f2f9… leo 157 assert "Test Video" in html_content
cd8f2f9… leo 158 assert "mermaid" in html_content.lower()
cd8f2f9… leo 159
cd8f2f9… leo 160 def test_export_with_chart_data(self, tmp_path):
cd8f2f9… leo 161 from video_processor.utils.export import export_all_formats
cd8f2f9… leo 162
cd8f2f9… leo 163 manifest = VideoManifest(
cd8f2f9… leo 164 video=VideoMetadata(title="Chart Test"),
cd8f2f9… leo 165 diagrams=[
cd8f2f9… leo 166 DiagramResult(
cd8f2f9… leo 167 frame_index=0,
cd8f2f9… leo 168 diagram_type=DiagramType.chart,
cd8f2f9… leo 169 confidence=0.9,
cd8f2f9… leo 170 chart_data={
cd8f2f9… leo 171 "labels": ["Q1", "Q2", "Q3"],
cd8f2f9… leo 172 "values": [100, 200, 150],
cd8f2f9… leo 173 "chart_type": "bar",
cd8f2f9… leo 174 },
cd8f2f9… leo 175 ),
cd8f2f9… leo 176 ],
cd8f2f9… leo 177 )
cd8f2f9… leo 178 (tmp_path / "results").mkdir()
cd8f2f9… leo 179 (tmp_path / "diagrams").mkdir()
cd8f2f9… leo 180
829e24a… leo 181 export_all_formats(tmp_path, manifest)
cd8f2f9… leo 182 # Chart should be reproduced
cd8f2f9… leo 183 chart_svg = tmp_path / "diagrams" / "diagram_0_chart.svg"
cd8f2f9… leo 184 assert chart_svg.exists()
cd8f2f9… leo 185
cd8f2f9… leo 186
cd8f2f9… leo 187 class TestGenerateHtmlReport:
cd8f2f9… leo 188 def test_html_contains_title(self, tmp_path):
cd8f2f9… leo 189 from video_processor.utils.export import generate_html_report
cd8f2f9… leo 190
cd8f2f9… leo 191 manifest = VideoManifest(
cd8f2f9… leo 192 video=VideoMetadata(title="My Meeting"),
cd8f2f9… leo 193 analysis_md="results/analysis.md",
cd8f2f9… leo 194 )
cd8f2f9… leo 195 (tmp_path / "results").mkdir()
cd8f2f9… leo 196 (tmp_path / "results" / "analysis.md").write_text("# My Meeting\nNotes here.")
cd8f2f9… leo 197
cd8f2f9… leo 198 path = generate_html_report(manifest, tmp_path)
cd8f2f9… leo 199 assert path is not None
cd8f2f9… leo 200 content = path.read_text()
cd8f2f9… leo 201 assert "My Meeting" in content
cd8f2f9… leo 202
cd8f2f9… leo 203 def test_html_includes_key_points(self, tmp_path):
cd8f2f9… leo 204 from video_processor.utils.export import generate_html_report
cd8f2f9… leo 205
cd8f2f9… leo 206 manifest = VideoManifest(
cd8f2f9… leo 207 video=VideoMetadata(title="Test"),
cd8f2f9… leo 208 key_points=[
cd8f2f9… leo 209 KeyPoint(point="First point", details="Detail 1"),
cd8f2f9… leo 210 KeyPoint(point="Second point"),
cd8f2f9… leo 211 ],
cd8f2f9… leo 212 )
cd8f2f9… leo 213 (tmp_path / "results").mkdir()
cd8f2f9… leo 214
cd8f2f9… leo 215 path = generate_html_report(manifest, tmp_path)
cd8f2f9… leo 216 content = path.read_text()
cd8f2f9… leo 217 assert "First point" in content
cd8f2f9… leo 218 assert "Detail 1" in content
cd8f2f9… leo 219 assert "Second point" in content
cd8f2f9… leo 220
cd8f2f9… leo 221 def test_html_includes_action_items(self, tmp_path):
cd8f2f9… leo 222 from video_processor.utils.export import generate_html_report
cd8f2f9… leo 223
cd8f2f9… leo 224 manifest = VideoManifest(
cd8f2f9… leo 225 video=VideoMetadata(title="Test"),
cd8f2f9… leo 226 action_items=[
cd8f2f9… leo 227 ActionItem(action="Do the thing", assignee="Bob", deadline="Friday"),
cd8f2f9… leo 228 ],
cd8f2f9… leo 229 )
cd8f2f9… leo 230 (tmp_path / "results").mkdir()
cd8f2f9… leo 231
cd8f2f9… leo 232 path = generate_html_report(manifest, tmp_path)
cd8f2f9… leo 233 content = path.read_text()
cd8f2f9… leo 234 assert "Do the thing" in content
cd8f2f9… leo 235 assert "Bob" in content
cd8f2f9… leo 236 assert "Friday" in content
cd8f2f9… leo 237
cd8f2f9… leo 238 def test_html_includes_mermaid_js(self, tmp_path):
cd8f2f9… leo 239 from video_processor.utils.export import generate_html_report
cd8f2f9… leo 240
cd8f2f9… leo 241 manifest = VideoManifest(
cd8f2f9… leo 242 video=VideoMetadata(title="Test"),
cd8f2f9… leo 243 diagrams=[
cd8f2f9… leo 244 DiagramResult(
cd8f2f9… leo 245 frame_index=0,
cd8f2f9… leo 246 mermaid="graph LR\n A-->B",
cd8f2f9… leo 247 )
cd8f2f9… leo 248 ],
cd8f2f9… leo 249 )
cd8f2f9… leo 250 (tmp_path / "results").mkdir()
cd8f2f9… leo 251
cd8f2f9… leo 252 path = generate_html_report(manifest, tmp_path)
cd8f2f9… leo 253 content = path.read_text()
cd8f2f9… leo 254 assert "mermaid" in content
cd8f2f9… leo 255 assert "A-->B" in content

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button