""" 時間軸渲染模組單元測試 對應 TDD.md: - UT-REN-01: 時間刻度演算法 - UT-REN-02: 節點避碰演算法 Version: 1.0.0 DocID: TDD-UT-REN-001 """ import pytest from datetime import datetime, timedelta from backend.schemas import Event, TimelineConfig, RenderResult, EventType from backend.renderer import ( TimeScaleCalculator, CollisionResolver, ThemeManager, TimelineRenderer, TimeUnit ) class TestTimeScaleCalculator: """時間刻度演算法測試""" def test_calculate_time_range(self): """測試時間範圍計算""" events = [ Event(id="1", title="E1", start=datetime(2024, 1, 1)), Event(id="2", title="E2", start=datetime(2024, 1, 10)) ] start, end = TimeScaleCalculator.calculate_time_range(events) assert start < datetime(2024, 1, 1) assert end > datetime(2024, 1, 10) def test_determine_time_unit_days(self): """測試天級別刻度判斷""" start = datetime(2024, 1, 1) end = datetime(2024, 1, 7) unit = TimeScaleCalculator.determine_time_unit(start, end) assert unit == TimeUnit.DAY def test_determine_time_unit_weeks(self): """測試週級別刻度判斷""" start = datetime(2024, 1, 1) end = datetime(2024, 3, 1) # 約 2 個月 unit = TimeScaleCalculator.determine_time_unit(start, end) assert unit == TimeUnit.WEEK def test_determine_time_unit_months(self): """測試月級別刻度判斷""" start = datetime(2024, 1, 1) end = datetime(2024, 6, 1) # 6 個月 unit = TimeScaleCalculator.determine_time_unit(start, end) assert unit == TimeUnit.MONTH def test_generate_tick_values_days(self): """測試天級別刻度生成""" start = datetime(2024, 1, 1) end = datetime(2024, 1, 5) ticks = TimeScaleCalculator.generate_tick_values(start, end, TimeUnit.DAY) assert len(ticks) >= 5 assert all(isinstance(t, datetime) for t in ticks) def test_generate_tick_values_months(self): """測試月級別刻度生成""" start = datetime(2024, 1, 1) end = datetime(2024, 6, 1) ticks = TimeScaleCalculator.generate_tick_values(start, end, TimeUnit.MONTH) assert len(ticks) >= 6 # 驗證是每月第一天 assert all(t.day == 1 for t in ticks) class TestCollisionResolver: """節點避碰演算法測試""" def test_no_overlapping_events(self): """測試無重疊事件""" events = [ Event(id="1", title="E1", start=datetime(2024, 1, 1), end=datetime(2024, 1, 2)), Event(id="2", title="E2", start=datetime(2024, 1, 3), end=datetime(2024, 1, 4)) ] resolver = CollisionResolver() layers = resolver.resolve_collisions(events) # 無重疊,都在第 0 層 assert layers["1"] == 0 assert layers["2"] == 0 def test_overlapping_events(self): """測試重疊事件分層""" events = [ Event(id="1", title="E1", start=datetime(2024, 1, 1), end=datetime(2024, 1, 5)), Event(id="2", title="E2", start=datetime(2024, 1, 3), end=datetime(2024, 1, 7)) ] resolver = CollisionResolver() layers = resolver.resolve_collisions(events) # 重疊,應該在不同層 assert layers["1"] != layers["2"] def test_group_based_layout(self): """測試基於群組的排版""" events = [ Event(id="1", title="E1", start=datetime(2024, 1, 1), group="A"), Event(id="2", title="E2", start=datetime(2024, 1, 1), group="B") ] resolver = CollisionResolver() layers = resolver.group_based_layout(events) # 不同群組,應該在不同層 assert layers["1"] != layers["2"] def test_empty_events(self): """測試空事件列表""" resolver = CollisionResolver() layers = resolver.resolve_collisions([]) assert layers == {} class TestThemeManager: """主題管理器測試""" def test_get_modern_theme(self): """測試現代主題""" from backend.schemas import ThemeStyle theme = ThemeManager.get_theme(ThemeStyle.MODERN) assert 'background' in theme assert 'text' in theme assert 'primary' in theme def test_get_all_themes(self): """測試所有主題可用性""" from backend.schemas import ThemeStyle for style in ThemeStyle: theme = ThemeManager.get_theme(style) assert theme is not None assert 'background' in theme class TestTimelineRenderer: """時間軸渲染器測試""" def test_render_basic_timeline(self): """測試基本時間軸渲染""" events = [ Event(id="1", title="Event 1", start=datetime(2024, 1, 1)), Event(id="2", title="Event 2", start=datetime(2024, 1, 5)) ] config = TimelineConfig() renderer = TimelineRenderer() result = renderer.render(events, config) assert result.success is True assert 'data' in result.data assert result.layout is not None def test_render_empty_timeline(self): """測試空白時間軸渲染""" renderer = TimelineRenderer() result = renderer.render([], TimelineConfig()) assert result.success is True assert 'data' in result.data def test_render_with_horizontal_direction(self): """測試水平方向渲染""" events = [Event(id="1", title="E1", start=datetime(2024, 1, 1))] config = TimelineConfig(direction='horizontal') renderer = TimelineRenderer() result = renderer.render(events, config) assert result.success is True def test_render_with_vertical_direction(self): """測試垂直方向渲染""" events = [Event(id="1", title="E1", start=datetime(2024, 1, 1))] config = TimelineConfig(direction='vertical') renderer = TimelineRenderer() result = renderer.render(events, config) assert result.success is True def test_render_with_different_themes(self): """測試不同主題渲染""" from backend.schemas import ThemeStyle events = [Event(id="1", title="E1", start=datetime(2024, 1, 1))] renderer = TimelineRenderer() for theme in [ThemeStyle.MODERN, ThemeStyle.CLASSIC]: config = TimelineConfig(theme=theme) result = renderer.render(events, config) assert result.success is True def test_render_with_grid(self): """測試顯示網格""" events = [Event(id="1", title="E1", start=datetime(2024, 1, 1))] config = TimelineConfig(show_grid=True) renderer = TimelineRenderer() result = renderer.render(events, config) assert result.success is True def test_render_single_event(self): """測試單一事件渲染""" events = [Event(id="1", title="Single", start=datetime(2024, 1, 1))] config = TimelineConfig() renderer = TimelineRenderer() result = renderer.render(events, config) assert result.success is True assert len(result.data['data']) == 1 def test_hover_text_generation(self): """測試提示訊息生成""" event = Event( id="1", title="Test Event", start=datetime(2024, 1, 1), end=datetime(2024, 1, 2), description="Test description" ) renderer = TimelineRenderer() hover_text = renderer._generate_hover_text(event) assert "Test Event" in hover_text assert "Test description" in hover_text if __name__ == "__main__": pytest.main([__file__, "-v"])