"""Slave GUI chat: newest-first transcript and fullscreen chat mode (offscreen Qt).""" from __future__ import annotations import os import sys from pathlib import Path import pytest os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") SLAVE_SRC = Path(__file__).resolve().parents[1] / "javis-slave" / "src" if str(SLAVE_SRC) not in sys.path: sys.path.insert(0, str(SLAVE_SRC)) QtWidgets = pytest.importorskip("PySide6.QtWidgets") QtTest = pytest.importorskip("PySide6.QtTest") from javis_slave import main_window # noqa: E402 @pytest.fixture(scope="module") def qapp(): return QtWidgets.QApplication.instance() or QtWidgets.QApplication([]) @pytest.fixture def screen(qapp): scr = main_window.MainScreen() scr.resize(900, 700) scr.show() yield scr scr.close() def _texts(scr) -> list[str]: """Bubble texts from the top of the transcript to the bottom.""" layout = scr.transcript_layout out = [] for i in range(layout.count()): row = layout.itemAt(i).widget() if row is not None: out.append(row.findChild(QtWidgets.QLabel).text()) return out def test_newest_message_is_on_top(screen): screen.set_turn("frage 1", "antwort 1") screen.set_turn("frage 2", "antwort 2") screen.show_error("kaputt") assert _texts(screen) == [ "⚠ kaputt", "antwort 2", "frage 2", "antwort 1", "frage 1"] def test_prune_drops_oldest_at_the_bottom(screen): for i in range(30): screen.set_turn(f"u{i}", f"a{i}") texts = _texts(screen) assert len(texts) == 40 assert texts[0] == "a29" assert "u0" not in texts and "u29" in texts def test_scrolls_to_top_on_new_message(screen): for i in range(12): screen.set_turn(f"u{i}", "x " * 200) QtTest.QTest.qWait(150) # lets the layout settle (and the first auto-scroll fire) vbar = screen.transcript_scroll.verticalScrollBar() assert vbar.maximum() > 0 vbar.setValue(vbar.maximum()) screen.set_turn("neu", "neu") QtTest.QTest.qWait(150) # the scroll is deferred by a 50 ms timer assert vbar.value() == vbar.minimum() def test_fullscreen_button_goes_fullscreen_and_keeps_orb_and_chat_visible(qapp): win = main_window.SlaveWindow() win.show_main() win.show() scr = win.main_screen scr.set_turn("frage", "antwort") try: assert not win.isFullScreen() assert scr.expand_btn.toolTip() == "Vollbild" scr.expand_btn.click() QtTest.QTest.qWait(50) assert win.isFullScreen() assert scr.orb.isVisible() and scr.transcript_scroll.isVisible() assert scr.expand_btn.toolTip() == "Vollbild beenden" scr.expand_btn.click() QtTest.QTest.qWait(50) assert not win.isFullScreen() assert scr.orb.isVisible() and scr.transcript_scroll.isVisible() assert scr.expand_btn.toolTip() == "Vollbild" finally: win.close() def test_esc_and_f11_keep_button_in_sync(qapp): win = main_window.SlaveWindow() win.show_main() win.show() try: win._toggle_fullscreen() # F11 QtTest.QTest.qWait(50) assert win.isFullScreen() and win.main_screen.expand_btn.toolTip() == "Vollbild beenden" win._leave_fullscreen() # Esc QtTest.QTest.qWait(50) assert not win.isFullScreen() and win.main_screen.expand_btn.toolTip() == "Vollbild" finally: win.close() def test_expand_icons_render(qapp): for kind in ("expand", "collapse"): img = main_window._icon(kind, "#cccccc", 26).pixmap(26, 26).toImage() assert any(img.pixelColor(x, y).alpha() > 0 for x in range(img.width()) for y in range(img.height()))