diff --git a/src/gui_2.py b/src/gui_2.py index 7a7394d..4affac0 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -2563,7 +2563,8 @@ def hello(): if self._scroll_disc_to_bottom: imgui.set_scroll_here_y(1.0) self._scroll_disc_to_bottom = False - imgui.end_child() + + imgui.end_child() def _render_synthesis_panel(self) -> None: """Renders a panel for synthesizing multiple discussion takes.""" diff --git a/tests/test_discussion_takes_gui.py b/tests/test_discussion_takes_gui.py new file mode 100644 index 0000000..0b9c7e2 --- /dev/null +++ b/tests/test_discussion_takes_gui.py @@ -0,0 +1,96 @@ +import pytest +from unittest.mock import MagicMock, patch, call +from src.gui_2 import App + +@pytest.fixture +def app_instance(): + with ( + patch('src.models.load_config', return_value={'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'}, 'projects': {}}), + patch('src.models.save_config'), + patch('src.gui_2.project_manager'), + patch('src.gui_2.session_logger'), + patch('src.gui_2.immapp.run'), + patch('src.app_controller.AppController._load_active_project'), + patch('src.app_controller.AppController._fetch_models'), + patch.object(App, '_load_fonts'), + patch.object(App, '_post_init'), + patch('src.app_controller.AppController._prune_old_logs'), + patch('src.app_controller.AppController.start_services'), + patch('src.api_hooks.HookServer'), + patch('src.ai_client.set_provider'), + patch('src.ai_client.reset_session') + ): + app = App() + # Setup project discussions + app.project = { + "discussion": { + "active": "main", + "discussions": { + "main": {"history": []}, + "take_1": {"history": []}, + "take_2": {"history": []} + } + } + } + app.active_discussion = "main" + app.is_viewing_prior_session = False + app.ui_disc_new_name_input = "" + app.ui_disc_truncate_pairs = 1 + yield app + +def test_render_discussion_tabs(app_instance): + """Verify that _render_discussion_panel uses tabs for discussions.""" + with patch('src.gui_2.imgui') as mock_imgui: + # Setup defaults for common imgui calls to avoid unpacking errors + mock_imgui.collapsing_header.return_value = True + mock_imgui.begin_combo.return_value = False + mock_imgui.input_text.return_value = (False, "") + mock_imgui.input_int.return_value = (False, 0) + mock_imgui.button.return_value = False + mock_imgui.checkbox.return_value = (False, False) + mock_imgui.begin_child.return_value = True + mock_imgui.selectable.return_value = (False, False) + + # Mock tab bar calls + mock_imgui.begin_tab_bar.return_value = True + mock_imgui.begin_tab_item.return_value = (False, False) + + app_instance._render_discussion_panel() + + # Check if begin_tab_bar was called + # This SHOULD fail if it's not implemented yet + mock_imgui.begin_tab_bar.assert_called_with("##discussion_tabs") + + # Check if begin_tab_item was called for each discussion + names = sorted(["main", "take_1", "take_2"]) + for name in names: + mock_imgui.begin_tab_item.assert_any_call(name) + +def test_switching_discussion_via_tabs(app_instance): + """Verify that clicking a tab switches the discussion.""" + with patch('src.gui_2.imgui') as mock_imgui, \ + patch('src.app_controller.AppController._switch_discussion') as mock_switch: + # Setup defaults + mock_imgui.collapsing_header.return_value = True + mock_imgui.begin_combo.return_value = False + mock_imgui.input_text.return_value = (False, "") + mock_imgui.input_int.return_value = (False, 0) + mock_imgui.button.return_value = False + mock_imgui.checkbox.return_value = (False, False) + mock_imgui.begin_child.return_value = True + mock_imgui.selectable.return_value = (False, False) + + mock_imgui.begin_tab_bar.return_value = True + + # Simulate 'take_1' being active/selected + def side_effect(name, flags=None): + if name == "take_1": + return (True, True) + return (False, True) + + mock_imgui.begin_tab_item.side_effect = side_effect + + app_instance._render_discussion_panel() + + # If implemented with tabs, this should be called + mock_switch.assert_called_with("take_1") diff --git a/tests/test_gui_discussion_tabs.py b/tests/test_gui_discussion_tabs.py index 406c8dd..6c8f41f 100644 --- a/tests/test_gui_discussion_tabs.py +++ b/tests/test_gui_discussion_tabs.py @@ -30,6 +30,7 @@ def test_discussion_tabs_rendered(mock_gui): mock_imgui.begin_tab_bar.return_value = True mock_imgui.begin_tab_item.return_value = (True, True) mock_imgui.input_text.return_value = (False, "") + mock_imgui.input_text_multiline.return_value = (False, "") mock_imgui.checkbox.return_value = (False, False) mock_imgui.input_int.return_value = (False, 0) diff --git a/tests/test_gui_synthesis.py b/tests/test_gui_synthesis.py new file mode 100644 index 0000000..6df1f6a --- /dev/null +++ b/tests/test_gui_synthesis.py @@ -0,0 +1,56 @@ +import pytest +from unittest.mock import MagicMock, patch, ANY +from src.gui_2 import App + +@pytest.fixture +def app_instance(): + with ( + patch('src.models.load_config', return_value={'ai': {'provider': 'gemini', 'model': 'gemini-2.5-flash-lite'}, 'projects': {}}), + patch('src.models.save_config'), + patch('src.gui_2.project_manager'), + patch('src.gui_2.session_logger'), + patch('src.gui_2.immapp.run'), + patch('src.app_controller.AppController._load_active_project'), + patch('src.app_controller.AppController._fetch_models'), + patch.object(App, '_load_fonts'), + patch.object(App, '_post_init'), + patch('src.app_controller.AppController._prune_old_logs'), + patch('src.app_controller.AppController.start_services'), + patch('src.api_hooks.HookServer'), + patch('src.ai_client.set_provider'), + patch('src.ai_client.reset_session') + ): + app = App() + app.project = { + "discussion": { + "active": "main", + "discussions": { + "main": {"history": []}, + "take_1": {"history": []}, + "take_2": {"history": []} + } + } + } + app.ui_synthesis_prompt = "Summarize these takes" + yield app + +def test_render_synthesis_panel(app_instance): + """Verify that _render_synthesis_panel renders checkboxes for takes and input for prompt.""" + with patch('src.gui_2.imgui') as mock_imgui: + mock_imgui.checkbox.return_value = (False, False) + mock_imgui.input_text_multiline.return_value = (False, app_instance.ui_synthesis_prompt) + mock_imgui.button.return_value = False + + # Call the method we are testing + app_instance._render_synthesis_panel() + + # 1. Assert imgui.checkbox is called for each take in project_dict['discussion']['discussions'] + discussions = app_instance.project['discussion']['discussions'] + for name in discussions: + mock_imgui.checkbox.assert_any_call(name, ANY) + + # 2. Assert imgui.input_text_multiline is called for the prompt + mock_imgui.input_text_multiline.assert_called_with("##synthesis_prompt", app_instance.ui_synthesis_prompt, ANY) + + # 3. Assert imgui.button is called for 'Generate Synthesis' + mock_imgui.button.assert_any_call("Generate Synthesis")