"""Review of the rail's unified play control: play/pause, stop, reset — and nothing else. Written against the requirement rather than the implementation: * the play-control group is exactly **play/pause**, **stop**, **reset** (no extra pause button, no role switch that turns the third button into a stop); * the primary button is a play/pause toggle in **both** views, and pressing it while a session runs pauses without stopping — the device stays connected; * stop ends the session (live) or leaves the replay (playback); * reset is one control with one meaning *per view*: the sensor tracker's in the live view (which owns a device session), the display's in the replay view (which owns none). """ from __future__ import annotations import ui_check from orisys_app_shared.i18n import t from tests.conftest import finish_run, running_window, show_window def test_the_group_is_exactly_play_pause_stop_reset(make_window): window = show_window(make_window(sensor_type="box", source_mode="camera"), (1440, 880)) toolbar = window.toolbar assert not hasattr(toolbar, "btn_pause"), "an extra pause button is still in the rail" assert hasattr(toolbar, "btn_stop"), "there is no dedicated stop button" assert not hasattr(toolbar, "set_reset_role"), ( "the reset button still switches role to stop; one control cannot be two" ) assert toolbar.btn_run.isCheckable(), "the play/pause control must be a toggle" window.close() def test_the_primary_button_pauses_and_does_not_stop(make_window): """The heart of the unification: one toggle, three states, and stop is not the toggle.""" window, stuck = running_window(make_window) try: assert window._session_active, "the fixture did not start a session" assert window.toolbar.btn_run.isChecked(), ( "a running session must show the transport as engaged (pause available)" ) assert "暂停" in window.toolbar.btn_run.text() or "Paus" in window.toolbar.btn_run.text(), ( f"a running session reads {window.toolbar.btn_run.text()!r}, not pause" ) # Press it: pause, not stop. window.toolbar.btn_run.setChecked(False) assert window._session_active, "pressing the transport ended the session" assert window._display_paused is True, "the window did not record the pause" assert stuck.is_paused is True, "the worker was not paused" assert "继续" in window.toolbar.btn_run.text() or "Resume" in window.toolbar.btn_run.text(), ( f"a paused session reads {window.toolbar.btn_run.text()!r}, not resume" ) # Press it again: resume. window.toolbar.btn_run.setChecked(True) assert window._display_paused is False assert stuck.is_paused is False, "the worker was not resumed" assert window._session_active, "resuming ended the session" finally: finish_run(window, stuck) def test_stop_ends_the_session(make_window): window, stuck = running_window(make_window) try: # A first frame is what makes the session a *running* one (the rail's reset and # the phase both key off it), so drive one before pressing stop. stuck.emit(ui_check._fake_packet(1)) window._on_run_tick() assert window.toolbar.btn_stop.isEnabled(), "stop is not reachable while running" window.toolbar.btn_stop.click() assert window._session_phase != "running", ( f"the stop button left the session in {window._session_phase!r}" ) assert not window.toolbar.btn_run.isChecked(), "the transport stayed engaged after stop" assert window._display_paused is False, "the pause flag survived the stop" finally: stuck.release() def test_a_paused_session_still_offers_both_stop_and_reset(make_window): """Pause holds the session; the device is still open, so both controls stay offered. This is the state the two rules are easiest to get wrong in: the transport is unchecked, which is not "stopped", so Stop must still end the session and the reset must still reach the device it is holding. """ window = show_window(make_window(sensor_type="box", source_mode="camera"), (1440, 880)) stuck = ui_check._StuckWorker() window._ensure_workers = lambda: None window._box_worker = stuck window._finger_worker = stuck window._start_run() window._run_timer.stop() try: # A first frame is the application's own path to ``running``, and the moment # the rail recomputes what the session can do (the reset keys off it), so the # session is reached through that path rather than stated directly. stuck.emit(ui_check._fake_packet(1)) window._on_run_tick() assert window._session_phase == "running" assert window.toolbar.btn_reset.isEnabled(), ( "a running session must offer the tracker reset before it is paused" ) window.toolbar.btn_run.setChecked(False) assert window._display_paused is True and window._running assert window.toolbar.btn_stop.isEnabled(), "no way out of the paused session" assert window.toolbar.btn_reset.isEnabled(), ( "pausing took the device's reset away: the device is still open" ) assert window.toolbar.reset_target() == "sensor" assert window.toolbar.btn_reset.text() == t("toolbar.reset_sensor") assert window.toolbar.btn_reset.toolTip() == t("toolbar.reset_sensor_tip") window.toolbar.btn_reset.click() assert stuck.reset_requests == 1, ( "the paused session's reset did not reach the device" ) window.toolbar.btn_stop.click() assert not window._running, "Stop did not end the paused session" finally: stuck.release() def test_reset_is_the_sensor_in_live_and_the_display_in_the_replay_view(make_window): """Reset stays one control, and each view gets the meaning it can act on. The enabled state of reset once frames are flowing is covered by ``test_ui_smoke.py::test_a_zeroing_is_recorded_only_after_the_worker_confirms_it`` (which drives ``_on_reset``); this test pins what the unification is about: reset and stop are two different controls, and the view decides which of the two things the reset acts on — the sensor tracker where a session owns a device, the display where there is nothing of the device to act on. """ window, stuck = running_window(make_window) try: assert window.toolbar.btn_reset.text() != window.toolbar.btn_stop.text(), ( "reset and stop must be distinguishable controls" ) assert window.toolbar.reset_target() == "sensor" assert window.toolbar.btn_stop.isEnabled(), "stop is not reachable during a session" finally: finish_run(window, stuck) replay = show_window(make_window(sensor_type="box", source_mode="camera"), (1440, 880)) replay.brand_bar.btn_playback.click() assert replay.toolbar.reset_target() == "display", ( "the replay view owns no device: its reset is the display's" ) assert replay.toolbar.btn_reset.isEnabled(), ( "the display reset is unavailable in the replay view" ) assert replay.toolbar.btn_reset.text() == t("toolbar.reset_display") assert hasattr(replay.toolbar, "btn_stop"), "the replay view has no stop control" assert replay.toolbar.btn_run.text().startswith("▶"), ( f"the replay transport idle reads {replay.toolbar.btn_run.text()!r}, not play" ) replay.close()