172 lines
5.7 KiB
Python
172 lines
5.7 KiB
Python
"""Offscreen UI smoke tests: construction, layout adaptation, theme tokens."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from tests.conftest import visible_descendants
|
|
|
|
RESOLUTIONS = ((1280, 720), (1600, 900), (1920, 1080))
|
|
|
|
|
|
def _show(window, size):
|
|
window.resize(*size)
|
|
window.show()
|
|
from PyQt5.QtWidgets import QApplication
|
|
|
|
QApplication.processEvents()
|
|
return window
|
|
|
|
|
|
@pytest.mark.parametrize(("theme", "sensor_type"), [
|
|
("dark", "box"),
|
|
("light", "box"),
|
|
("dark", "finger"),
|
|
("light", "finger"),
|
|
])
|
|
def test_window_builds_in_every_theme(make_window, theme, sensor_type):
|
|
window = _show(make_window(theme=theme, sensor_type=sensor_type), (1400, 880))
|
|
assert window.windowTitle()
|
|
assert window.content_splitter.count() == 4
|
|
|
|
|
|
@pytest.mark.parametrize("size", RESOLUTIONS)
|
|
def test_panels_fit_within_window(make_window, size):
|
|
window = _show(
|
|
make_window(sensor_type="finger", show_cal_panel=True),
|
|
size,
|
|
)
|
|
sizes = window.content_splitter.sizes()
|
|
panels = [
|
|
window.mesh3d_panel,
|
|
window.arrow_panel,
|
|
window.force_plots,
|
|
window.cal_panel,
|
|
]
|
|
for panel, width in zip(panels, sizes):
|
|
assert panel.isVisible()
|
|
assert width >= panel.minimumWidth(), (
|
|
f"{panel.objectName() or type(panel).__name__} too narrow: {width}"
|
|
)
|
|
assert window.force_plots.width() >= window.force_plots.minimumWidth()
|
|
assert window.arrow_panel.width() >= window.arrow_panel.minimumWidth()
|
|
|
|
|
|
@pytest.mark.parametrize("size", RESOLUTIONS)
|
|
def test_children_stay_inside_their_parent(make_window, size):
|
|
window = _show(make_window(sensor_type="finger", show_cal_panel=True), size)
|
|
oversize = []
|
|
for child in visible_descendants(window):
|
|
parent = child.parentWidget()
|
|
if parent is None or not parent.isVisible():
|
|
continue
|
|
geometry = child.geometry()
|
|
if geometry.width() > parent.width() + 1 or geometry.height() > parent.height() + 1:
|
|
oversize.append(
|
|
f"{type(child).__name__} {geometry.width()}x{geometry.height()}"
|
|
f" > parent {parent.width()}x{parent.height()}"
|
|
)
|
|
assert not oversize, oversize
|
|
|
|
|
|
def test_theme_tokens_have_identical_keys():
|
|
from orisys_app_shared.theme import build_qss, load_colors
|
|
|
|
dark = set(load_colors("dark"))
|
|
light = set(load_colors("light"))
|
|
assert dark == light, {"dark_only": sorted(dark - light), "light_only": sorted(light - dark)}
|
|
assert len(build_qss("dark")) > 1000
|
|
assert len(build_qss("light")) > 1000
|
|
|
|
|
|
def test_arrow_canvas_differs_from_accent():
|
|
"""The pure-color arrow canvas must not collide with the accent pen color."""
|
|
from orisys_app_shared.theme import load_colors
|
|
|
|
for theme in ("dark", "light"):
|
|
colors = load_colors(theme)
|
|
assert colors["arrow_canvas"] != colors["accent"], theme
|
|
|
|
|
|
def test_disabled_buttons_use_muted_text_not_hover_background():
|
|
from orisys_app_shared.theme import build_qss, load_colors
|
|
|
|
for theme in ("dark", "light"):
|
|
colors = load_colors(theme)
|
|
qss = build_qss(theme)
|
|
assert f"color: {colors['text_muted']}" in qss, theme
|
|
# The old rule painted disabled buttons with the brighter hover color.
|
|
assert f"background-color: {colors['button_hover']};\n border-color: {colors['panel_border']}" not in qss
|
|
|
|
|
|
def test_splitter_sizes_round_trip_through_prefs(make_window):
|
|
window = _show(make_window(), (1440, 880))
|
|
window.content_splitter.setSizes([300, 420, 520, 0])
|
|
window._persist_prefs()
|
|
stored = window.prefs["splitter_sizes"]
|
|
assert len(stored) == 4
|
|
assert stored[2] > stored[0]
|
|
|
|
|
|
def test_status_chip_tracks_phase(make_window):
|
|
window = _show(make_window(), (1400, 880))
|
|
assert window._session_phase == "idle"
|
|
window._set_phase("running")
|
|
assert window.status_chip.text()
|
|
assert "status_running" not in window.status_chip.text()
|
|
window._set_phase("nonexistent-phase")
|
|
assert window._session_phase == "idle"
|
|
|
|
|
|
def test_run_tooltip_explains_disabled_reason(make_window):
|
|
window = _show(make_window(source_mode="video", video_path=""), (1400, 880))
|
|
assert not window.toolbar.btn_run.isEnabled()
|
|
assert window.toolbar.btn_run.toolTip()
|
|
|
|
window.toolbar.set_video_path("C:/tmp/demo.mp4")
|
|
window._update_run_enabled()
|
|
assert window.toolbar.btn_run.isEnabled()
|
|
|
|
|
|
def test_toolbar_run_button_is_labelled(make_window):
|
|
window = _show(make_window(), (1400, 880))
|
|
label = window.toolbar.btn_run.text()
|
|
assert label.strip()
|
|
assert label != "▶"
|
|
window.toolbar.set_run_checked(True)
|
|
assert window.toolbar.btn_run.text() != label
|
|
|
|
|
|
def test_theme_switch_updates_panels(make_window):
|
|
window = _show(make_window(), (1400, 880))
|
|
window._set_theme("light")
|
|
assert window._theme == "light"
|
|
window._set_theme("dark")
|
|
assert window._theme == "dark"
|
|
|
|
|
|
def test_cal_panel_records_and_lists_samples(make_window):
|
|
window = _show(make_window(), (1400, 880))
|
|
cal = window.cal_panel
|
|
cal.set_running(True)
|
|
assert cal.btn_record.isEnabled()
|
|
assert cal.btn_record.text()
|
|
|
|
from orisys_app_shared.force.calibration_io import CalSample
|
|
|
|
cal._store.upsert("fn", CalSample(weight_n=1.0, raw_mean=9.7, n=120, std=0.02))
|
|
cal._refresh_table()
|
|
assert cal.table.rowCount() == 1
|
|
assert not cal.empty_hint.isVisible()
|
|
|
|
|
|
def test_force_plots_render_demo_data(make_window):
|
|
window = _show(make_window(), (1440, 880))
|
|
for i in range(60):
|
|
window.force_plots.push(i / 30.0, float(i), -1.0, 1.0, unit="N")
|
|
from PyQt5.QtWidgets import QApplication
|
|
|
|
QApplication.processEvents()
|
|
assert window.force_plots.history.sample_count > 0
|
|
assert all(label.text() for label in window.force_plots.value_labels)
|