c6c16df753
让界面构建可脱离主窗口独立测试。界面与绑定检查 13 项通过,运行入口尚未接线。
167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
"""Prepared shell surfaces work without the main window or business wiring."""
|
|
|
|
import pytest
|
|
from orisys_app_shared.i18n import get_translator, set_locale, t
|
|
from orisys_app_shared.sensors import scan_sensor_catalog
|
|
from PySide6.QtCore import Qt
|
|
from PySide6.QtWidgets import QMainWindow
|
|
|
|
from orisys_studio.ui import sensor_picker as picker_module
|
|
from orisys_studio.ui.window_menus import WindowMenus
|
|
from orisys_studio.ui.window_ui import WindowUi
|
|
|
|
|
|
@pytest.fixture
|
|
def window(qt_app):
|
|
original_locale = get_translator().locale
|
|
widget = QMainWindow()
|
|
yield widget
|
|
widget.close()
|
|
widget.deleteLater()
|
|
set_locale(original_locale)
|
|
|
|
|
|
@pytest.fixture
|
|
def surface(window, tmp_path, monkeypatch):
|
|
catalog = scan_sensor_catalog(
|
|
shipped_root=tmp_path / "shipped",
|
|
user_root=tmp_path / "user",
|
|
)
|
|
monkeypatch.setattr(picker_module, "scan_sensor_catalog", lambda: catalog)
|
|
ui = WindowUi(window, theme="dark")
|
|
yield ui
|
|
# The picker is intentionally not parented until a sensor dialog adopts it.
|
|
ui.sensor_picker.close()
|
|
ui.sensor_picker.deleteLater()
|
|
|
|
|
|
def menus(window, **changes):
|
|
values = dict(
|
|
show_cal=False,
|
|
show_finger_3d=True,
|
|
show_fps=True,
|
|
show_diagnostics=False,
|
|
settings_expanded=False,
|
|
)
|
|
values.update(changes)
|
|
return WindowMenus(window, **values)
|
|
|
|
|
|
def test_central_surface_preserves_panel_order_and_ownership(window, surface):
|
|
assert window.centralWidget() is surface.central
|
|
layout = surface.central.layout()
|
|
assert [layout.itemAt(i).widget() for i in range(layout.count())] == [
|
|
surface.brand_bar,
|
|
surface.toolbar,
|
|
surface.analysis_progress,
|
|
surface.content_splitter,
|
|
]
|
|
assert layout.spacing() == 8
|
|
margins = layout.contentsMargins()
|
|
assert (margins.left(), margins.top(), margins.right(), margins.bottom()) == (
|
|
10,
|
|
) * 4
|
|
splitter = surface.content_splitter
|
|
panels = [
|
|
surface.mesh3d_panel,
|
|
surface.arrow_panel,
|
|
surface.force_plots,
|
|
surface.cal_panel,
|
|
]
|
|
assert splitter.orientation() == Qt.Orientation.Horizontal
|
|
assert [splitter.widget(i) for i in range(splitter.count())] == panels
|
|
assert all(panel.parentWidget() is splitter for panel in panels)
|
|
assert not splitter.childrenCollapsible()
|
|
assert surface.sensor_picker.parentWidget() is None
|
|
assert surface.status_chip is surface.toolbar.status_chip
|
|
|
|
|
|
def test_surface_refresh_keeps_3d_lazy_and_global_theme_unchanged(qt_app, surface):
|
|
stylesheet = qt_app.styleSheet()
|
|
plot_background = surface.force_plots.plot_widget.backgroundBrush().color()
|
|
surface.set_theme("light")
|
|
surface.retranslate()
|
|
assert surface.force_plots.plot_widget.backgroundBrush().color() != plot_background
|
|
assert qt_app.styleSheet() == stylesheet
|
|
assert surface.mesh3d_panel.view is None
|
|
assert surface.brand_bar.btn_live.text() == t("brand.nav_live")
|
|
|
|
|
|
@pytest.mark.parametrize("locale", ["zh_CN", "en_US"])
|
|
def test_menu_order_and_labels_match_the_shell(window, locale):
|
|
set_locale(locale)
|
|
ui = menus(window)
|
|
ui.retranslate()
|
|
assert [action.text() for action in window.menuBar().actions()] == [
|
|
t("menu.file"),
|
|
t("menu.session"),
|
|
t("menu.analysis"),
|
|
t("menu.view"),
|
|
t("menu.help"),
|
|
]
|
|
assert ui.session_menu.actions() == [
|
|
ui.act_run,
|
|
ui.act_stop,
|
|
ui.act_preview,
|
|
ui.act_reset,
|
|
ui.act_pause,
|
|
]
|
|
assert ui.act_analysis_setup.toolTip() == t("menu.analysis_setup_tip")
|
|
assert ui.act_recompute.toolTip() == t("menu.recompute_tip")
|
|
|
|
|
|
def test_shortcuts_are_available_without_any_business_host(window):
|
|
ui = menus(window)
|
|
assert [
|
|
action.shortcut().toString()
|
|
for action in (
|
|
ui.act_run,
|
|
ui.act_stop,
|
|
ui.act_preview,
|
|
ui.act_reset,
|
|
ui.act_pause,
|
|
ui.act_reset_view,
|
|
ui.act_play,
|
|
)
|
|
] == ["F5", "Esc", "F6", "Ctrl+R", "F7", "F8", "Space"]
|
|
assert ui.act_play.shortcutContext() == Qt.ShortcutContext.ApplicationShortcut
|
|
assert ui.act_play in window.actions()
|
|
seen = []
|
|
ui.act_run.triggered.connect(lambda: seen.append("run"))
|
|
ui.act_run.trigger()
|
|
assert seen == ["run"]
|
|
# These actions have no device, calibration or analysis handlers to invoke yet.
|
|
ui.act_reset.trigger()
|
|
ui.act_recompute.trigger()
|
|
assert not hasattr(window, "session")
|
|
|
|
|
|
def test_menu_initial_checks_and_appearance_refresh_are_separate(window):
|
|
ui = menus(window, show_cal=True, show_fps=False, settings_expanded=True)
|
|
assert ui.act_cal_panel.isChecked()
|
|
assert ui.act_finger_3d.isChecked()
|
|
assert not ui.act_show_fps.isChecked()
|
|
assert not ui.act_show_diagnostics.isChecked()
|
|
assert ui.act_settings.isChecked()
|
|
assert not ui.act_recompute.isChecked()
|
|
ui.sync_appearance(theme="light", language="en_US")
|
|
assert ui.act_theme_light.isChecked() and not ui.act_theme_dark.isChecked()
|
|
assert ui.act_lang_en.isChecked() and not ui.act_lang_zh.isChecked()
|
|
ui.act_theme_dark.trigger()
|
|
assert ui.act_theme_dark.isChecked() and not ui.act_theme_light.isChecked()
|
|
ui.act_lang_zh.trigger()
|
|
assert ui.act_lang_zh.isChecked() and not ui.act_lang_en.isChecked()
|
|
assert ui.act_cal_panel.isChecked()
|
|
|
|
|
|
def test_text_refresh_preserves_controller_owned_action_state(window):
|
|
ui = menus(window)
|
|
ui.act_reset.setText("context-dependent reset")
|
|
ui.act_pause.setChecked(True)
|
|
ui.act_run.setEnabled(False)
|
|
ui.retranslate()
|
|
ui.sync_appearance(theme="dark", language="zh_CN")
|
|
assert ui.act_reset.text() == "context-dependent reset"
|
|
assert ui.act_pause.isChecked()
|
|
assert not ui.act_run.isEnabled()
|