Private
Public Access
0
0

fix(gui): Force newline in discussion entries to prevent squashed layout

- Insert imgui.new_line() before rendering discussion content.
- Ensures the Markdown renderer inherits the full horizontal width of the panel.
- Definitively fixes vertical squashing of tables and long text blocks.
This commit is contained in:
2026-06-02 15:42:04 -04:00
parent fee41032b6
commit 4d8e949720
11 changed files with 485 additions and 44 deletions
+83
View File
@@ -0,0 +1,83 @@
import unittest
from unittest.mock import MagicMock, patch
import sys
import os
# Ensure project root is in path so we can import src.gui_2
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if project_root not in sys.path:
sys.path.insert(0, project_root)
class TestMarkdownTableWidth(unittest.TestCase):
def test_render_discussion_entry_full_width(self):
"""
Verify that render_discussion_entry calls imgui.dummy with the full available width.
This is critical for ensuring that the background and Markdown content expand to
the full width of the discussion panel.
"""
# Mock all dependencies to avoid side effects and complex setup during import/execution
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, \
patch('src.gui_2.ui_shared') as mock_ui_shared, \
patch('src.gui_2.project_manager') as mock_pm, \
patch('src.gui_2.render_thinking_trace') as mock_rtt, \
patch('src.gui_2.render_discussion_entry_read_mode') as mock_rderm:
# 1. Setup available width and coordinates
expected_width = 850.0
mock_avail = MagicMock()
mock_avail.x = expected_width
mock_imgui.get_content_region_avail.return_value = mock_avail
# Mock ImVec2 to return a simple tuple for easier assertion
mock_imgui.ImVec2.side_effect = lambda x, y: (x, y)
# Mock screen position
mock_p_min = MagicMock()
mock_p_min.x = 100.0
mock_p_min.y = 200.0
mock_imgui.get_cursor_screen_pos.return_value = mock_p_min
# Mock rect max
mock_p_max = MagicMock()
mock_imgui.get_item_rect_max.return_value = mock_p_max
# 2. Mock drawing and style dependencies
mock_draw_list = MagicMock()
mock_imgui.get_window_draw_list.return_value = mock_draw_list
mock_style = MagicMock()
mock_style.window_padding.x = 10.0
mock_imgui.get_style.return_value = mock_style
# 3. Mock app and entry state
mock_app = MagicMock()
mock_app.disc_roles = ["User", "Assistant"]
entry = {
"role": "User",
"content": "Hello world",
"collapsed": False,
"read_mode": False
}
# Mock combo and other interactive elements to prevent deep branching
mock_imgui.begin_combo.return_value = False
mock_imgui.button.return_value = False
mock_imgui.input_text_multiline.return_value = (False, entry["content"])
# 4. Import the function within the patch context
# Note: We import here to ensure mocks are in place during module initialization if needed
from src.gui_2 import render_discussion_entry
# 5. Execute the function
render_discussion_entry(mock_app, entry, 0)
# 6. Verification
# The function should call imgui.dummy(imgui.ImVec2(full_width, 0)) at line 3153
# Our mock ImVec2 returns (full_width, 0)
mock_imgui.dummy.assert_any_call((expected_width, 0.0))
if __name__ == '__main__':
unittest.main()
+44
View File
@@ -0,0 +1,44 @@
import inspect
import sys
import os
# Ensure project root is in path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
try:
from src.gui_2 import App, render_discussion_entry, render_thinking_trace
import src.gui_2
except ImportError as e:
print(f"FAILURE: Could not import from src.gui_2: {e}")
sys.exit(1)
def test_gui_monolithic_symbols():
# Verify App is importable
assert App is not None
# Verify render_discussion_entry is in src.gui_2
assert hasattr(src.gui_2, 'render_discussion_entry'), "render_discussion_entry missing from src.gui_2"
# Verify it's defined in src.gui_2, not imported
mod = inspect.getmodule(render_discussion_entry)
assert mod is not None, "Could not determine module for render_discussion_entry"
assert mod.__name__ == 'src.gui_2', f"render_discussion_entry expected in src.gui_2, but found in {mod.__name__}"
# Verify render_thinking_trace is in src.gui_2
assert hasattr(src.gui_2, 'render_thinking_trace'), "render_thinking_trace missing from src.gui_2"
# Verify it's defined in src.gui_2, not imported
mod = inspect.getmodule(render_thinking_trace)
assert mod is not None, "Could not determine module for render_thinking_trace"
assert mod.__name__ == 'src.gui_2', f"render_thinking_trace expected in src.gui_2, but found in {mod.__name__}"
if __name__ == "__main__":
try:
test_gui_monolithic_symbols()
print("SUCCESS: Symbols are correctly defined in src.gui_2 local namespace.")
except AssertionError as e:
print(f"FAILURE: {e}")
sys.exit(1)
except Exception as e:
print(f"ERROR: {e}")
sys.exit(1)
+54
View File
@@ -0,0 +1,54 @@
import pytest
from unittest.mock import patch, MagicMock
from src.gui_2 import render_text_viewer_window
def test_text_viewer_window_id_stability():
# Setup a mock app with necessary state
app = MagicMock()
app.show_windows = {"Text Viewer": True}
app.text_viewer_title = "Custom Title"
app.text_viewer_content = "Some content"
app.text_viewer_type = "text"
app.ui_editing_slices_file = None
# Patch all dependencies
with patch('src.gui_2.imgui') as mock_imgui, \
patch('src.gui_2.markdown_helper') as mock_md, \
patch('src.gui_2.imscope') as mock_scope:
# Setup mock returns
mock_imgui.begin.return_value = (True, True)
mock_imgui.checkbox.return_value = (False, True)
render_text_viewer_window(app)
# Verify imgui.begin was called with the stable ID suffix
args, _ = mock_imgui.begin.call_args
window_title = args[0]
assert "###Text_Viewer_Stable" in window_title
assert window_title.startswith("Custom Title")
def test_text_viewer_window_default_title_id_stability():
# Setup a mock app with default title (None)
app = MagicMock()
app.show_windows = {"Text Viewer": True}
app.text_viewer_title = None
app.text_viewer_content = "Some content"
app.text_viewer_type = "text"
app.ui_editing_slices_file = None
with patch('src.gui_2.imgui') as mock_imgui, \
patch('src.gui_2.markdown_helper') as mock_md, \
patch('src.gui_2.imscope') as mock_scope:
# Setup mock returns
mock_imgui.begin.return_value = (True, True)
mock_imgui.checkbox.return_value = (False, True)
render_text_viewer_window(app)
# Verify imgui.begin was called with the stable ID suffix
args, _ = mock_imgui.begin.call_args
window_title = args[0]
assert "###Text_Viewer_Stable" in window_title
assert window_title.startswith("Text Viewer")
+45
View File
@@ -0,0 +1,45 @@
import unittest
from unittest.mock import MagicMock, patch
import sys
import os
# Ensure project root is in path
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if project_root not in sys.path:
sys.path.insert(0, project_root)
class TestMonolithicLayout(unittest.TestCase):
def test_render_discussion_entry_full_width_logic(self):
"""Verify that render_discussion_entry uses full width and a newline."""
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, \
patch('src.gui_2.project_manager') as mock_pm, \
patch('src.gui_2.render_thinking_trace') as mock_rtt, \
patch('src.gui_2.render_discussion_entry_read_mode') as mock_rderm:
expected_width = 850.0
mock_avail = MagicMock()
mock_avail.x = expected_width
mock_imgui.get_content_region_avail.return_value = mock_avail
mock_imgui.ImVec2.side_effect = lambda x, y: (x, y)
mock_app = MagicMock()
mock_app.disc_roles = ["User", "Assistant"]
entry = {"role": "User", "content": "test", "collapsed": False, "read_mode": False}
mock_imgui.begin_combo.return_value = False
mock_imgui.button.return_value = False
mock_imgui.input_text_multiline.return_value = (False, "test")
from src.gui_2 import render_discussion_entry
render_discussion_entry(mock_app, entry, 0)
# 1. Verify group expansion
mock_imgui.dummy.assert_any_call((expected_width, 0.0))
# 2. Verify newline to prevent squashing
assert mock_imgui.new_line.called, "imgui.new_line() was not called to prevent squashing"
if __name__ == '__main__':
unittest.main()
+29
View File
@@ -0,0 +1,29 @@
import pytest
from unittest.mock import patch, MagicMock
from src.imgui_scopes import _ScopeId
import src.imgui_scopes as imgui_scopes
def test_scope_id_string():
with patch('src.imgui_scopes.imgui') as mock_imgui:
sid = _ScopeId("test_id")
with sid:
pass
mock_imgui.push_id.assert_called_once_with("test_id")
mock_imgui.pop_id.assert_called_once()
def test_scope_id_int():
with patch('src.imgui_scopes.imgui') as mock_imgui:
# Python type hint is str, but we test runtime resilience
sid = _ScopeId(1234)
with sid:
pass
# Verify it was converted to string to prevent low-level crashes
mock_imgui.push_id.assert_called_once_with("1234")
mock_imgui.pop_id.assert_called_once()
def test_id_helper_function():
with patch('src.imgui_scopes.imgui') as mock_imgui:
with imgui_scopes.id(42):
pass
mock_imgui.push_id.assert_called_once_with("42")
mock_imgui.pop_id.assert_called_once()