"""Shell surfaces work independently and retain their behavior when wired.""" 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() def test_shell_uses_the_surface_widgets_and_actions(make_window): shell = make_window() assert shell.centralWidget() is shell.ui.central for name in ( "brand_bar", "sensor_picker", "toolbar", "status_chip", "analysis_progress", "content_splitter", "mesh3d_panel", "arrow_panel", "force_plots", "cal_panel", ): assert getattr(shell, name) is getattr(shell.ui, name) assert shell.file_menu is shell.menus.file_menu assert shell.act_play is shell.menus.act_play assert shell.act_reset is shell.menus.act_reset assert shell.mesh3d_panel.view is None assert not shell.session.is_running def test_shell_menu_commands_dispatch_once(make_window, monkeypatch): from orisys_studio.app_window import StudioMainWindow calls = [] def handler(name): return lambda self: calls.append(name) commands = { "run": "_on_run_shortcut", "stop": "_on_stop_shortcut", "preview": "_on_preview_shortcut", "reset": "_on_reset", "reset_view": "_on_reset_view", } for name, method in commands.items(): monkeypatch.setattr(StudioMainWindow, method, handler(name)) shell = make_window() calls.clear() for name in commands: action = getattr(shell.menus, f"act_{name}") # No device is attached; exercise dispatch without availability policy. action.setEnabled(True) action.trigger() assert calls == list(commands) def test_shell_appearance_actions_refresh_widgets_and_menus(make_window, qt_app): original_locale = get_translator().locale original_stylesheet = qt_app.styleSheet() shell = make_window() try: background = shell.force_plots.plot_widget.backgroundBrush().color() shell.menus.act_theme_light.trigger() assert shell.prefs["theme"] == "light" assert shell.force_plots.plot_widget.backgroundBrush().color() != background assert shell.menus.act_theme_light.isChecked() assert not shell.menus.act_theme_dark.isChecked() shell.menus.act_lang_en.trigger() assert shell.prefs["language"] == "en_US" assert shell.file_menu.title() == t("menu.file") assert shell.brand_bar.btn_live.text() == t("brand.nav_live") assert shell.act_reset.text() == t("menu.reset") assert shell.toolbar.btn_reset.text() == t("toolbar.reset_sensor") shell.layout_ctl.apply_preset("replay") assert shell.act_reset.text() == t("menu.reset_display") assert shell.toolbar.btn_reset.text() == t("toolbar.reset_display") assert shell.menus.act_lang_en.isChecked() assert not shell.menus.act_lang_zh.isChecked() assert shell.mesh3d_panel.view is None finally: set_locale(original_locale) qt_app.setStyleSheet(original_stylesheet)