refactor: 拆出 Studio 状态显示并记录分步迁移计划
让状态显示独立于主窗口,保留录制快照注入入口。验证结果和完整门禁限制见 docs/architecture/studio-window-refactor-plan.zh.md。
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
"""Status presentation without a main window or running acquisition."""
|
||||
|
||||
from dataclasses import replace
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from orisys_app_shared.i18n import t
|
||||
from orisys_app_shared.record.experiment import RecordStats
|
||||
|
||||
from orisys_studio.status_presenter import RailState, StatusPresenter
|
||||
from orisys_studio.ui.toolbar import ToolbarWidget
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def surface(qt_app):
|
||||
toolbar = ToolbarWidget(theme="dark")
|
||||
yield toolbar, StatusPresenter(toolbar)
|
||||
toolbar.close()
|
||||
toolbar.deleteLater()
|
||||
|
||||
|
||||
def rail(**changes):
|
||||
return replace(
|
||||
RailState(
|
||||
sensor_type="finger",
|
||||
config_name="finger/r01/sensor.json",
|
||||
view_preset="live",
|
||||
video_path="D:/clips/box.mp4",
|
||||
has_video=True,
|
||||
replay_warnings="video warning",
|
||||
calibration_summary="calibration",
|
||||
),
|
||||
**changes,
|
||||
)
|
||||
|
||||
|
||||
def test_live_summary_keeps_video_details_out_and_reports_capture_cost(surface):
|
||||
toolbar, presenter = surface
|
||||
state = rail(
|
||||
show_fps=True,
|
||||
fps=16.67,
|
||||
process_ms=58.4,
|
||||
capture_fps=30.0,
|
||||
capture_dropped=7,
|
||||
)
|
||||
presenter.refresh_rail(state)
|
||||
text = toolbar.source_summary.text()
|
||||
assert "box.mp4" not in text
|
||||
assert "video warning" not in toolbar.source_summary.toolTip()
|
||||
assert "16.7 FPS" in text and "58 ms" in text
|
||||
assert "30.0" in text and "(-7)" in text
|
||||
assert state.view_preset == "live"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"loaded,key",
|
||||
[
|
||||
(True, "status.rail_video_name"),
|
||||
(False, "status.rail_recent_video"),
|
||||
],
|
||||
)
|
||||
def test_replay_distinguishes_open_video_from_remembered_path(surface, loaded, key):
|
||||
toolbar, presenter = surface
|
||||
presenter.refresh_rail(rail(view_preset="replay", has_video=loaded))
|
||||
assert toolbar.source_summary.text() == t(key, name="box.mp4")
|
||||
assert ("video warning" in toolbar.source_summary.toolTip()) is loaded
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"progress,key",
|
||||
[
|
||||
("reused", "status.rail_result_reused"),
|
||||
("complete", "status.rail_result_complete"),
|
||||
("running", "status.rail_result_running"),
|
||||
("", "status.rail_result_ready"),
|
||||
],
|
||||
)
|
||||
def test_result_identity_belongs_to_the_result_not_the_picker(surface, progress, key):
|
||||
toolbar, presenter = surface
|
||||
presenter.refresh_rail(
|
||||
rail(
|
||||
view_preset="replay",
|
||||
result_sensor_type="box",
|
||||
result_progress=progress,
|
||||
)
|
||||
)
|
||||
text = toolbar.source_summary.text()
|
||||
assert text.startswith(t("status.rail_result_profile", type="box"))
|
||||
assert text.endswith(t(key))
|
||||
assert "FINGER" in toolbar.btn_sensor.text()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"version,fallback,axes,key,labels",
|
||||
[
|
||||
("", None, None, "status.uncalibrated", ""),
|
||||
("confirmed", None, None, "status.calibrated", ""),
|
||||
("confirmed", True, (True, True, True), "status.uncalibrated", ""),
|
||||
("confirmed", False, None, "status.uncalibrated", ""),
|
||||
("confirmed", False, (False, False, False), "status.uncalibrated", ""),
|
||||
("confirmed", False, (True, True, True), "status.calibrated_all", ""),
|
||||
("confirmed", False, (True, False, True), "status.calibrated_axes", "FN+FY"),
|
||||
],
|
||||
)
|
||||
def test_calibration_summary_preserves_unknown_raw_and_partial_axes(
|
||||
version,
|
||||
fallback,
|
||||
axes,
|
||||
key,
|
||||
labels,
|
||||
):
|
||||
assert StatusPresenter.calibration_summary(version, fallback, axes) == t(
|
||||
key, axes=labels
|
||||
)
|
||||
|
||||
|
||||
def test_phase_repaints_after_a_theme_change_without_changing_the_phase(surface):
|
||||
toolbar, presenter = surface
|
||||
presenter.refresh_phase("connecting", theme="dark")
|
||||
assert toolbar.status_chip.text() == f"● {t('status.phase_connecting')}"
|
||||
presenter.refresh_phase("connecting", theme="light")
|
||||
assert toolbar.status_chip.text() == f"● {t('status.phase_connecting')}"
|
||||
|
||||
|
||||
def test_record_close_and_all_gap_counts_remain_visible(surface):
|
||||
toolbar, presenter = surface
|
||||
stats = RecordStats(
|
||||
frames=12,
|
||||
duration_s=2.5,
|
||||
dropped_data=1,
|
||||
dropped_video=2,
|
||||
dropped_gaps=3,
|
||||
unrecorded_gaps=4,
|
||||
write_errors=1,
|
||||
last_error="disk full",
|
||||
)
|
||||
recording = SimpleNamespace(
|
||||
recorder=SimpleNamespace(directory="D:/recordings/run", stats=lambda: stats),
|
||||
is_recording=False,
|
||||
report_pending=True,
|
||||
last_report=SimpleNamespace(stats=RecordStats()),
|
||||
)
|
||||
snapshot = presenter.record_stats(recording)
|
||||
assert snapshot == ("closing", stats, "run")
|
||||
presenter.refresh_record(*snapshot)
|
||||
displayed = toolbar.record_chip.stats()
|
||||
assert displayed["state"] == "closing"
|
||||
assert displayed["dropped"] == 10
|
||||
assert displayed["dropped_gaps"] == 7
|
||||
assert displayed["errors"] == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"failed,errors,expected",
|
||||
[
|
||||
(False, 0, "done"),
|
||||
(True, 0, "error"),
|
||||
(False, 1, "error"),
|
||||
],
|
||||
)
|
||||
def test_finished_recording_uses_the_final_report(surface, failed, errors, expected):
|
||||
toolbar, presenter = surface
|
||||
stats = RecordStats(failed=failed, write_errors=errors)
|
||||
recording = SimpleNamespace(
|
||||
recorder=SimpleNamespace(directory=None),
|
||||
is_recording=False,
|
||||
report_pending=False,
|
||||
last_report=SimpleNamespace(stats=stats),
|
||||
)
|
||||
assert presenter.record_stats(recording) == (expected, stats, "")
|
||||
presenter.refresh_record(expected, stats, "")
|
||||
assert toolbar.record_chip.state() == expected
|
||||
Reference in New Issue
Block a user