"""Edge case tests for parsers.""" import pytest from pathlib import Path from development_hub.parsers.todos_parser import TodosParser from development_hub.parsers.goals_parser import GoalsParser, MilestonesParser, GoalsSaver from development_hub.models.todo import Todo, TodoList from development_hub.models.goal import Goal, GoalList, Milestone, MilestoneStatus class TestTodosParserEdgeCases: """Edge cases for TodosParser.""" def test_empty_file(self, tmp_path): """Handles empty file.""" todos_file = tmp_path / "todos.md" todos_file.write_text("") parser = TodosParser(todos_file) result = parser.parse() assert isinstance(result, TodoList) assert len(result.all_todos) == 0 def test_frontmatter_only(self, tmp_path): """Handles file with only frontmatter.""" todos_file = tmp_path / "todos.md" todos_file.write_text("""--- type: todos project: test --- """) parser = TodosParser(todos_file) result = parser.parse() assert len(result.all_todos) == 0 assert parser.frontmatter is not None def test_malformed_checkbox(self, tmp_path): """Handles malformed checkbox lines.""" todos_file = tmp_path / "todos.md" todos_file.write_text("""--- type: todos --- # TODOs ## Active Tasks ### High Priority - [ ] Valid task #high - [ ] Extra space #high - [x] Completed #high - [-] Invalid checkbox #high - [ ] Another valid #high """) parser = TodosParser(todos_file) result = parser.parse() # Should parse valid ones, skip malformed valid_tasks = [t for t in result.all_todos if "Valid" in t.text or "Another" in t.text or "Completed" in t.text] assert len(valid_tasks) >= 2 def test_todo_with_special_characters(self, tmp_path): """Handles special characters in todo text.""" todos_file = tmp_path / "todos.md" todos_file.write_text("""--- type: todos --- # TODOs ## Active Tasks ### High Priority - [ ] Task with "quotes" and 'apostrophes' #high - [ ] Task with tags #high - [ ] Task with unicode: cafe #high - [ ] Task with emoji: test #high """) parser = TodosParser(todos_file) result = parser.parse() # Save and reload parser.save(result) parser2 = TodosParser(todos_file) result2 = parser2.parse() assert len(result.high_priority) == len(result2.high_priority) def test_priority_extraction(self, tmp_path): """Correctly extracts priority tags.""" todos_file = tmp_path / "todos.md" todos_file.write_text("""--- type: todos --- # TODOs ## Active Tasks ### High Priority - [ ] Task #high - [ ] Task with milestone @M1 #high ### Medium Priority - [ ] Medium task #medium ### Low Priority - [ ] Low task #low """) parser = TodosParser(todos_file) result = parser.parse() assert len(result.high_priority) == 2 assert len(result.medium_priority) == 1 assert len(result.low_priority) == 1 def test_milestone_extraction(self, tmp_path): """Correctly extracts milestone tags.""" todos_file = tmp_path / "todos.md" todos_file.write_text("""--- type: todos --- # TODOs ## Active Tasks ### High Priority - [ ] Task for M1 @M1 #high - [ ] Task for M2 @M2 #high - [ ] Task for M10 @M10 #high - [ ] Task no milestone #high """) parser = TodosParser(todos_file) result = parser.parse() milestones = [t.milestone for t in result.high_priority] assert "M1" in milestones assert "M2" in milestones assert "M10" in milestones assert None in milestones def test_completed_date_preservation(self, tmp_path): """Preserves completion dates.""" todos_file = tmp_path / "todos.md" todos_file.write_text("""--- type: todos --- # TODOs ## Active Tasks ### High Priority ## Completed - [x] Done task #high (2026-01-15) - [x] Another done #medium (2025-12-31) """) parser = TodosParser(todos_file) result = parser.parse() assert len(result.completed) == 2 # Dates should be preserved in text or parsed # Save and verify dates preserved parser.save(result) content = todos_file.read_text() assert "2026-01-15" in content assert "2025-12-31" in content class TestGoalsParserEdgeCases: """Edge cases for GoalsParser.""" def test_empty_sections(self, tmp_path): """Handles empty goal sections.""" goals_file = tmp_path / "goals.md" goals_file.write_text("""--- type: goals --- # Goals ## Active ## Future ## Non-Goals """) parser = GoalsParser(goals_file) result = parser.parse() assert len(result.active) == 0 assert len(result.future) == 0 assert len(result.non_goals) == 0 def test_missing_sections(self, tmp_path): """Handles missing sections.""" goals_file = tmp_path / "goals.md" goals_file.write_text("""--- type: goals --- # Goals ## Active - [ ] Only active goal #high """) parser = GoalsParser(goals_file) result = parser.parse() assert len(result.active) == 1 assert len(result.future) == 0 assert len(result.non_goals) == 0 def test_three_state_checkbox(self, tmp_path): """Correctly parses three-state checkboxes.""" goals_file = tmp_path / "goals.md" goals_file.write_text("""--- type: goals --- # Goals ## Active - [ ] Not achieved #high - [~] Partially achieved #medium - [x] Fully achieved #low """) parser = GoalsParser(goals_file) result = parser.parse() assert result.active[0].completed == False assert result.active[0].partial == False assert result.active[1].completed == False assert result.active[1].partial == True assert result.active[2].completed == True assert result.active[2].partial == False def test_goal_priority_extraction(self, tmp_path): """Correctly extracts goal priorities.""" goals_file = tmp_path / "goals.md" goals_file.write_text("""--- type: goals --- # Goals ## Active - [ ] High priority goal #high - [ ] Medium priority goal #medium - [ ] Low priority goal #low - [ ] Default priority goal """) parser = GoalsParser(goals_file) result = parser.parse() priorities = [g.priority for g in result.active] assert "high" in priorities assert "medium" in priorities assert "low" in priorities class TestMilestonesParserEdgeCases: """Edge cases for MilestonesParser.""" def test_milestone_without_deliverables(self, tmp_path): """Handles milestone with no deliverables table.""" milestones_file = tmp_path / "milestones.md" milestones_file.write_text("""--- type: milestones --- # Milestones #### M1: Simple Milestone **Target**: January 2026 **Status**: In Progress (50%) Just a description, no deliverables table. --- """) parser = MilestonesParser(milestones_file) result = parser.parse() assert len(result) == 1 assert result[0].id == "M1" assert len(result[0].deliverables) == 0 def test_progress_parsing(self, tmp_path): """Correctly parses progress percentages.""" milestones_file = tmp_path / "milestones.md" milestones_file.write_text("""--- type: milestones --- # Milestones #### M1: Zero Progress **Target**: Jan 2026 **Status**: Not Started --- #### M2: Half Done **Target**: Jan 2026 **Status**: In Progress (50%) --- #### M3: Complete **Target**: Jan 2026 **Status**: Completed (100%) --- """) parser = MilestonesParser(milestones_file) result = parser.parse() by_id = {m.id: m for m in result} assert by_id["M1"].progress == 0 assert by_id["M2"].progress == 50 assert by_id["M3"].progress == 100 def test_status_parsing(self, tmp_path): """Correctly parses all milestone statuses.""" milestones_file = tmp_path / "milestones.md" milestones_file.write_text("""--- type: milestones --- # Milestones #### M1: Not Started **Target**: Jan 2026 **Status**: Not Started --- #### M2: Planning **Target**: Jan 2026 **Status**: Planning (10%) --- #### M3: In Progress **Target**: Jan 2026 **Status**: In Progress (60%) --- #### M4: Complete **Target**: Jan 2026 **Status**: Completed (100%) --- """) parser = MilestonesParser(milestones_file) result = parser.parse() by_id = {m.id: m for m in result} assert by_id["M1"].status == MilestoneStatus.NOT_STARTED assert by_id["M2"].status == MilestoneStatus.PLANNING assert by_id["M3"].status == MilestoneStatus.IN_PROGRESS assert by_id["M4"].status == MilestoneStatus.COMPLETE def test_deliverable_status_parsing(self, tmp_path): """Correctly parses deliverable statuses.""" milestones_file = tmp_path / "milestones.md" milestones_file.write_text("""--- type: milestones --- # Milestones #### M1: With Deliverables **Target**: Jan 2026 **Status**: In Progress (33%) | Deliverable | Status | |-------------|--------| | Task A | Done | | Task B | In Progress | | Task C | Not Started | --- """) parser = MilestonesParser(milestones_file) result = parser.parse() deliverables = result[0].deliverables assert len(deliverables) == 3 from development_hub.models.goal import DeliverableStatus statuses = {d.name: d.status for d in deliverables} assert statuses["Task A"] == DeliverableStatus.DONE assert statuses["Task B"] == DeliverableStatus.IN_PROGRESS assert statuses["Task C"] == DeliverableStatus.NOT_STARTED class TestTodoListOperations: """Test TodoList model operations.""" def test_add_todo_to_correct_priority(self): """Adding todo goes to correct priority list.""" todo_list = TodoList(source_file="/tmp/test.md") high_todo = Todo(text="High task", priority="high", completed=False) med_todo = Todo(text="Medium task", priority="medium", completed=False) low_todo = Todo(text="Low task", priority="low", completed=False) todo_list.add_todo(high_todo) todo_list.add_todo(med_todo) todo_list.add_todo(low_todo) assert high_todo in todo_list.high_priority assert med_todo in todo_list.medium_priority assert low_todo in todo_list.low_priority def test_remove_todo(self): """Removing todo from list.""" todo_list = TodoList(source_file="/tmp/test.md") todo = Todo(text="Test task", priority="high", completed=False) todo_list.add_todo(todo) assert todo in todo_list.high_priority todo_list.remove_todo(todo) assert todo not in todo_list.high_priority def test_get_by_milestone(self): """Get todos by milestone.""" todo_list = TodoList(source_file="/tmp/test.md") t1 = Todo(text="Task 1", priority="high", completed=False, milestone="M1") t2 = Todo(text="Task 2", priority="high", completed=False, milestone="M1") t3 = Todo(text="Task 3", priority="high", completed=False, milestone="M2") t4 = Todo(text="Task 4", priority="high", completed=False, milestone=None) todo_list.add_todo(t1) todo_list.add_todo(t2) todo_list.add_todo(t3) todo_list.add_todo(t4) m1_todos = todo_list.get_by_milestone("M1") assert len(m1_todos) == 2 assert t1 in m1_todos assert t2 in m1_todos def test_active_todos_excludes_completed(self): """active_todos excludes completed items.""" todo_list = TodoList(source_file="/tmp/test.md") active = Todo(text="Active", priority="high", completed=False) completed = Todo(text="Done", priority="high", completed=True) todo_list.add_todo(active) todo_list.completed.append(completed) assert active in todo_list.active_todos assert completed not in todo_list.active_todos class TestGoalListOperations: """Test GoalList model operations.""" def test_create_goal_list(self): """Create a GoalList with goals.""" goal_list = GoalList( active=[Goal(text="Active 1", priority="high")], future=[Goal(text="Future 1", priority="medium")], non_goals=[Goal(text="Non-goal 1", priority="low")], ) assert len(goal_list.active) == 1 assert len(goal_list.future) == 1 assert len(goal_list.non_goals) == 1 def test_goal_with_partial_state(self): """Goal with partial state.""" goal = Goal( text="Partial goal", priority="high", completed=False, partial=True, ) assert goal.completed == False assert goal.partial == True if __name__ == "__main__": pytest.main([__file__, "-v"])