8f11340b38
Per post_module_taxonomy_de_cruft_20260627 Phase 2 (FR7). Each
'from src.models import X' for a moved class is rewritten to
'from src.<destination> import X':
Ticket, Track, WorkerContext, TrackState, TrackMetadata,
ThinkingSegment, EMPTY_TRACK_STATE -> src.mma
ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles,
ProjectScreenshots, ProjectDiscussion, EMPTY_PROJECT_CONTEXT -> src.project
FileItem, Preset, ContextPreset, ContextFileEntry,
NamedViewPreset -> src.project_files
Tool, ToolPreset -> src.tool_presets
BiasProfile -> src.tool_bias
TextEditorConfig, ExternalEditorConfig,
EMPTY_TEXT_EDITOR_CONFIG -> src.external_editor
Persona -> src.personas
WorkspaceProfile -> src.workspace_manager
MCPServerConfig, MCPConfiguration, VectorStoreConfig,
RAGConfig, load_mcp_config -> src.mcp_client
NOT touched (kept on src.models; Phase 3 or Phase 4 will move them):
GenerateRequest, ConfirmRequest, DEFAULT_TOOL_CATEGORIES, Metadata, PROVIDERS
Migration was performed by the one-time script
scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_imports.py
which uses a class-to-module map and re.sub() to rewrite each
'from src.models import X' line.
Total: 85 import lines rewritten across 71 files.
Note: this commit depends on the v2 SHIPPED work
(origin/tier2/module_taxonomy_refactor_20260627) being merged into
this branch NEXT. On master (without the v2 SHIPPED commits), the
destination modules do not exist and these imports would fail.
164 lines
6.9 KiB
Python
164 lines
6.9 KiB
Python
import pytest
|
|
from unittest.mock import MagicMock, patch
|
|
from src import gui_2
|
|
from src.gui_2 import App
|
|
from src.mma import Track
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_mock_app(mock_app: App):
|
|
mock_app._discussion_names_dirty = True
|
|
mock_app._discussion_names_cache = []
|
|
mock_app.active_track = Track(id="track-1", description="Test Track", tickets=[])
|
|
mock_app.active_tickets = []
|
|
mock_app.ui_files_base_dir = "."
|
|
mock_app.disc_roles = ["User", "AI"]
|
|
mock_app.active_discussion = "main"
|
|
mock_app.project = {"discussion": {"discussions": {"main": {"history": []}}}}
|
|
|
|
def test_add_ticket_logic(mock_app: App):
|
|
# Test the ticket form submission logic directly without rendering
|
|
mock_app._show_add_ticket_form = True
|
|
mock_app.ui_new_ticket_id = "T-001"
|
|
mock_app.ui_new_ticket_desc = "Test Description"
|
|
mock_app.ui_new_ticket_target = "test.py"
|
|
mock_app.ui_new_ticket_deps = "T-000"
|
|
|
|
with patch('src.project_manager.save_track_state') as mock_save:
|
|
# Directly call the form submission logic by simulating button click behavior
|
|
new_ticket = {
|
|
"id": mock_app.ui_new_ticket_id,
|
|
"description": mock_app.ui_new_ticket_desc,
|
|
"status": "todo",
|
|
"assigned_to": "tier3-worker",
|
|
"target_file": mock_app.ui_new_ticket_target,
|
|
"depends_on": [d.strip() for d in mock_app.ui_new_ticket_deps.split(",") if d.strip()]
|
|
}
|
|
mock_app.active_tickets.append(new_ticket)
|
|
mock_app._show_add_ticket_form = False
|
|
|
|
# Verify ticket was added
|
|
assert len(mock_app.active_tickets) == 1
|
|
t = mock_app.active_tickets[0]
|
|
assert t["id"] == "T-001"
|
|
assert t["description"] == "Test Description"
|
|
assert t["target_file"] == "test.py"
|
|
assert t["depends_on"] == ["T-000"]
|
|
assert t["status"] == "todo"
|
|
assert t["assigned_to"] == "tier3-worker"
|
|
|
|
# Verify form was closed
|
|
assert not mock_app._show_add_ticket_form
|
|
|
|
def test_delete_ticket_logic(mock_app: App):
|
|
# Test the ticket deletion logic directly
|
|
mock_app.active_tickets = [
|
|
{"id": "T-001", "status": "todo", "depends_on": []},
|
|
{"id": "T-002", "status": "todo", "depends_on": ["T-001"]}
|
|
]
|
|
mock_app.ui_selected_ticket_id = "T-001"
|
|
|
|
with patch('src.project_manager.save_track_state') as mock_save:
|
|
# Simulate the delete button click logic (from MMA dashboard)
|
|
ticket_id_to_delete = mock_app.ui_selected_ticket_id
|
|
mock_app.active_tickets = [t for t in mock_app.active_tickets if str(t.get('id', '')) != ticket_id_to_delete]
|
|
mock_app.ui_selected_ticket_id = None
|
|
|
|
# Verify T-001 was deleted
|
|
assert len(mock_app.active_tickets) == 1
|
|
assert mock_app.active_tickets[0]["id"] == "T-002"
|
|
# Note: Dependencies are NOT auto-cleaned on delete - that's actual behavior
|
|
|
|
def test_track_discussion_toggle(mock_app: App):
|
|
with (
|
|
patch('src.gui_2.imgui') as mock_imgui,
|
|
patch('src.theme_2.imgui', new=mock_imgui),
|
|
patch('src.imgui_scopes.imgui', new=mock_imgui),
|
|
patch('src.markdown_helper.imgui', new=mock_imgui),
|
|
patch('src.markdown_helper.imgui_md') as mock_imgui_md,
|
|
patch('src.gui_2.markdown_helper.imgui_md', new=mock_imgui_md),
|
|
patch('src.gui_2.imscope') as mock_imscope,
|
|
patch('src.gui_2.project_manager.load_track_history', return_value=["@2026-03-01 12:00:00\n[User]\nTrack Hello"]) as mock_load,
|
|
patch('src.gui_2.project_manager.str_to_entry', side_effect=lambda s, roles: {"ts": "12:00", "role": "User", "content": s.split("\n")[-1]}),
|
|
patch.object(mock_app.controller, '_flush_disc_entries_to_project') as mock_flush,
|
|
patch.object(mock_app.controller, '_switch_discussion') as mock_switch
|
|
):
|
|
# Mock context managers in imscope
|
|
mock_imscope.id.return_value.__enter__.return_value = None
|
|
mock_imscope.child.return_value.__enter__.return_value = True
|
|
mock_imscope.tab_item.return_value.__enter__.return_value = (True, True)
|
|
mock_imscope.style_color.return_value.__enter__.return_value = None
|
|
mock_imscope.style_var.return_value.__enter__.return_value = None
|
|
|
|
# Track calls to ensure we only return 'changed=True' once to avoid loops
|
|
calls = {"Track Discussion": 0}
|
|
def checkbox_side_effect(label, value):
|
|
if label == "Track Discussion":
|
|
calls[label] += 1
|
|
# Only return True for 'changed' on the first call in the test
|
|
changed = (calls[label] == 1)
|
|
return changed, True
|
|
return False, value
|
|
|
|
mock_imgui.checkbox.side_effect = checkbox_side_effect
|
|
mock_imgui.begin_combo.return_value = False
|
|
mock_imgui.selectable.return_value = (False, False)
|
|
mock_imgui.button.return_value = False
|
|
mock_imgui.combo.return_value = (False, 0)
|
|
mock_imgui.begin_tab_bar.return_value = True
|
|
mock_imgui.collapsing_header.return_value = True # For Discussions header
|
|
mock_imgui.input_text.side_effect = lambda label, value, *args, **kwargs: (False, value)
|
|
mock_imgui.input_text_multiline.side_effect = lambda label, value, *args, **kwargs: (False, value)
|
|
mock_imgui.input_int.side_effect = lambda label, value, *args, **kwargs: (False, value)
|
|
mock_imgui.drag_int.side_effect = lambda label, value, *args, **kwargs: (False, value)
|
|
mock_imgui.begin_child.return_value = True
|
|
mock_imgui.get_window_height.return_value = 800.0
|
|
mock_imgui.get_io.return_value.mouse_delta.y = 0.0
|
|
mock_imgui.is_item_active.return_value = False
|
|
mock_imgui.get_style.return_value.color_.return_value = (1, 1, 1, 1)
|
|
mock_imgui.Col_ = MagicMock()
|
|
mock_imgui.set_scroll_here_y.return_value = None
|
|
# Mock clipper to avoid the while loop hang
|
|
mock_clipper = MagicMock()
|
|
mock_clipper.step.side_effect = [True, False]
|
|
mock_clipper.display_start = 0
|
|
mock_clipper.display_end = 0
|
|
mock_imgui.ListClipper.return_value = mock_clipper
|
|
|
|
gui_2.render_discussion_hub(mock_app)
|
|
|
|
assert mock_app._track_discussion_active
|
|
mock_flush.assert_called()
|
|
mock_load.assert_called_with("track-1", ".")
|
|
assert len(mock_app.disc_entries) == 1
|
|
assert mock_app.disc_entries[0]["content"] == "[User]\nTrack Hello"
|
|
|
|
# Now toggle OFF
|
|
calls["Track Discussion"] = 0 # Reset for next call
|
|
def checkbox_off_side_effect(label, value):
|
|
if label == "Track Discussion":
|
|
calls[label] += 1
|
|
return (calls[label] == 1), False
|
|
return False, value
|
|
mock_imgui.checkbox.side_effect = checkbox_off_side_effect
|
|
mock_clipper.step.side_effect = [True, False] # Reset clipper
|
|
|
|
gui_2.render_discussion_hub(mock_app)
|
|
|
|
assert not mock_app._track_discussion_active
|
|
mock_switch.assert_called_with(mock_app.active_discussion)
|
|
|
|
def test_push_mma_state_update(mock_app: App):
|
|
mock_app.active_tickets = [{"id": "T-001", "description": "desc", "status": "todo", "assigned_to": "tier3-worker", "depends_on": []}]
|
|
with patch('src.project_manager.save_track_state') as mock_save, \
|
|
patch('src.project_manager.load_track_state', return_value=None):
|
|
mock_app.controller._push_mma_state_update()
|
|
|
|
assert len(mock_app.active_track.tickets) == 1
|
|
assert mock_app.active_track.tickets[0].id == "T-001"
|
|
assert mock_save.called
|
|
args, kwargs = mock_save.call_args
|
|
assert args[0] == "track-1"
|
|
state = args[1]
|
|
assert state.metadata.id == "track-1"
|
|
assert state.tasks == mock_app.active_track.tickets
|