fix(gui): explicit child size for comms_scroll and prior_scroll prevents early clipping
ROOT CAUSE: When child windows used ImVec2(0, 0) for auto-fill, the
child's reported height was unstable inside tab items (especially when
the parent tab was inside a tab_bar inside a window). Result: the
scrollable child rendered with a fixed smaller height, showing only the
first half of the content, with empty space below.
FIX: Use imgui.get_content_region_avail() to compute explicit dimensions
and pass them to begin_child. Now the child fills the full available area
inside the tab content.
- render_comms_history_panel: avail.x, avail.y
- render_prior_session_view: same, plus added entry count indicator next
to the Exit Prior Session button ({N} entries) for at-a-glance info
Tests:
- test_comms_scroll_no_clipping.py: verifies comms_scroll child uses
explicit (non-zero) size
- test_prior_session_no_clipping.py: same for prior_scroll child
- test_log_management_first_open.py: minor cleanup
- 42/42 broad regression pass
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.gui_2 import render_comms_history_panel
|
||||
|
||||
def test_comms_history_renders_all_entries_not_just_early_subset(app_instance):
|
||||
entries = [{"direction": "in", "kind": "response", "provider": "x", "model": "y", "ts": f"00:00:0{i}", "payload": {"text": f"Entry {i} content"}} for i in range(50)]
|
||||
app_instance._comms_log_cache = entries
|
||||
app_instance.is_viewing_prior_session = False
|
||||
app_instance.perf_profiling_enabled = False
|
||||
app_instance._scroll_comms_to_bottom = False
|
||||
with patch("src.gui_2.imgui") as mock_imgui, patch("src.gui_2.imscope") as mock_imscope:
|
||||
mock_imgui.text_colored = MagicMock()
|
||||
mock_imgui.text = MagicMock()
|
||||
mock_imgui.same_line = MagicMock()
|
||||
mock_imgui.separator = MagicMock()
|
||||
mock_imgui.begin_child = MagicMock(return_value=True)
|
||||
mock_imgui.end_child = MagicMock()
|
||||
mock_imgui.button = MagicMock(return_value=False)
|
||||
mock_imgui.push_style_color = MagicMock()
|
||||
mock_imgui.pop_style_color = MagicMock()
|
||||
mock_imgui.set_scroll_here_y = MagicMock()
|
||||
mock_imgui.get_content_region_avail = MagicMock(return_value=MagicMock(x=800.0, y=600.0))
|
||||
mock_imgui.ImVec2 = lambda *a: ("ImVec2", a)
|
||||
mock_imgui.ImVec4 = lambda *a: ("ImVec4", a)
|
||||
mock_imscope.child = MagicMock()
|
||||
mock_imscope.child.return_value.__enter__ = MagicMock()
|
||||
mock_imscope.child.return_value.__exit__ = MagicMock()
|
||||
mock_imscope.style_color = MagicMock()
|
||||
mock_imscope.style_color.return_value.__enter__ = MagicMock()
|
||||
mock_imscope.style_color.return_value.__exit__ = MagicMock()
|
||||
app_instance.controller = MagicMock()
|
||||
app_instance.controller.event_queue.put = MagicMock()
|
||||
app_instance.cb_load_prior_log = MagicMock()
|
||||
try:
|
||||
render_comms_history_panel(app_instance)
|
||||
except Exception as e:
|
||||
import pytest
|
||||
pytest.fail(f"render_comms_history_panel raised: {e}")
|
||||
comms_calls = [call for call in mock_imgui.begin_child.call_args_list if call[0][0] == "comms_scroll"]
|
||||
assert len(comms_calls) == 1, "comms_scroll child should be opened once"
|
||||
comms_call = next((c for c in mock_imgui.begin_child.call_args_list if c[0][0] == "comms_scroll"), None)
|
||||
assert comms_call is not None, "comms_scroll child should be among begin_child calls"
|
||||
args, _ = comms_call
|
||||
size_arg = args[1]
|
||||
if isinstance(size_arg, tuple) and len(size_arg) == 2 and isinstance(size_arg[0], str):
|
||||
actual = size_arg[1]
|
||||
else:
|
||||
actual = size_arg
|
||||
assert actual != (0, 0), f"comms_scroll child should use explicit content region size, got {actual}"
|
||||
@@ -27,6 +27,8 @@ def test_log_management_populates_registry_on_first_open(app_instance):
|
||||
mock_imgui.end_table = lambda: None
|
||||
mock_imscope.window.return_value.__enter__.return_value = (True, True)
|
||||
app_instance.cb_load_prior_log = MagicMock()
|
||||
app_instance.controller = MagicMock()
|
||||
del app_instance.controller._log_registry
|
||||
render_log_management(app_instance)
|
||||
assert app_instance._log_registry is not None
|
||||
assert "s1" in app_instance._log_registry.data, f"Registry should be populated on first open. Got: {list(app_instance._log_registry.data.keys())}"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.gui_2 import render_prior_session_view
|
||||
|
||||
def test_prior_session_view_opens_scroll_child_with_explicit_size(app_instance):
|
||||
app_instance.prior_disc_entries = [{"role": "User", "content": f"entry {i}", "collapsed": False, "ts": f"t{i}"} for i in range(30)]
|
||||
app_instance.perf_profiling_enabled = False
|
||||
with patch("src.gui_2.imgui") as mock_imgui, patch("src.gui_2.imscope") as mock_imscope, patch("src.gui_2.theme") as mock_theme:
|
||||
_avail = MagicMock(); _avail.x = 800.0; _avail.y = 600.0
|
||||
mock_imgui.get_content_region_avail = MagicMock(return_value=_avail)
|
||||
mock_imscope.style_color = MagicMock()
|
||||
mock_imscope.style_color.return_value.__enter__ = MagicMock()
|
||||
mock_imscope.style_color.return_value.__exit__ = MagicMock()
|
||||
mock_imscope.child = MagicMock()
|
||||
mock_imscope.child.return_value.__enter__ = MagicMock()
|
||||
mock_imscope.child.return_value.__exit__ = MagicMock()
|
||||
mock_imscope.id = MagicMock()
|
||||
mock_imscope.id.return_value.__enter__ = MagicMock()
|
||||
mock_imscope.id.return_value.__exit__ = MagicMock()
|
||||
mock_imgui.button = MagicMock(return_value=False)
|
||||
mock_imgui.same_line = MagicMock()
|
||||
mock_imgui.text_colored = MagicMock()
|
||||
mock_imgui.text = MagicMock()
|
||||
mock_imgui.separator = MagicMock()
|
||||
_mock_clipper = MagicMock()
|
||||
_mock_clipper.step.side_effect = [True, False]
|
||||
_mock_clipper.display_start = 0
|
||||
_mock_clipper.display_end = 30
|
||||
mock_imgui.ListClipper = MagicMock(return_value=_mock_clipper)
|
||||
mock_imgui.ListClipper.return_value.begin = MagicMock()
|
||||
mock_theme.ai_text_style = MagicMock()
|
||||
mock_theme.ai_text_style.return_value.__enter__ = MagicMock()
|
||||
mock_theme.ai_text_style.return_value.__exit__ = MagicMock()
|
||||
app_instance.controller = MagicMock()
|
||||
try:
|
||||
render_prior_session_view(app_instance)
|
||||
except Exception as e:
|
||||
import pytest
|
||||
pytest.fail(f"render_prior_session_view raised: {e}")
|
||||
assert mock_imscope.child.called, "prior_scroll child should be opened"
|
||||
call_args = mock_imscope.child.call_args
|
||||
kwargs = call_args[1] if len(call_args) > 1 else {}
|
||||
size_x, size_y = kwargs.get("size_x", 0), kwargs.get("size_y", 0)
|
||||
assert size_x == 800.0 and size_y == 600.0, f"prior_scroll should use explicit content region size, got ({size_x}, {size_y})"
|
||||
Reference in New Issue
Block a user