"""The UI element manifest against a real window, in every view. ``docs/ui-elements.json`` is the machine-readable half of the layout contract in ``docs/ui-layout.md``: the prose says who owns the arrangement, the manifest says *which elements are supposed to exist*. These tests make that claim falsifiable against a real ``StudioMainWindow``: * every element the view declares exists, is reachable from the window and is the recorded Qt class (plus the recorded structural attributes, and the recorded ``objectName``); * the view's panes are the ones the manifest says are on screen; * the drift report — visible named widgets the manifest accounts for neither as an element identity nor as a declared stylesheet token — equals the drift the manifest records; * every condition the manifest uses is exercised by some window here, so no element is permanently skipped; * the transport group the manifest declares is the behaviour of a *live* session: a running session keeps the tracker reset available and reads "pause", and a paused session reads "resume". That is the defect class this artifact exists to prevent — a state assertion about widgets passing while the visible result is wrong. A deliberate asymmetry: the existence assertions are hard (a missing element fails), while the drift report is compared with a record. The manifest is expected to lag the code, and the point of the report is to *show* the difference rather than to pretend it does not exist. """ from __future__ import annotations import pytest import ui_check from orisys_app_shared.i18n import t from orisys_studio.ui import elements from PySide6.QtWidgets import QApplication from tests.conftest import finish_run, show_window VIEWS = ("live", "replay", "calibration") SIZE = (1440, 880) #: The four windows the conditional elements need. ``box`` needs nothing; the others are the #: states a condition names (see ``elements.CONDITIONS``). SCENARIOS = { "box": {}, "finger": {"sensor_type": "finger"}, "video": {"sensor_type": "box", "source_mode": "video"}, "dialog": {"sensor_type": "box"}, } #: The condition each scenario must satisfy, so a scenario cannot pass by satisfying nothing. SCENARIO_CONDITIONS = { "box": set(), "finger": {"mesh3d_view", "mesh3d_panel"}, "video": {"video_source"}, "dialog": {"sensor_dialog_open"}, } def _prepare(scenario: str, window) -> None: """Put a window into the state its scenario names.""" if scenario == "video": window.toolbar.set_video_path("C:/tmp/ui-elements-demo.mp4") window._set_source_mode("video") elif scenario == "dialog": window._open_sensor_dialog() QApplication.processEvents() def _window(make_window, scenario: str, view: str): window = show_window(make_window(**SCENARIOS[scenario]), SIZE) window.layout_ctl.apply_preset(view) _prepare(scenario, window) return window def _running_session(make_window): """A window with a live session that has actually started. ``conftest.running_window`` states the phase directly, which skips the first-frame transition (and with it the rail update) that this test is about; this is the same setup without the shortcut, so the transport and the reset reach their states through the application's own path. """ window = show_window(make_window(sensor_type="box", source_mode="camera"), SIZE) stuck = ui_check._StuckWorker() window._ensure_workers = lambda: None window._box_worker = stuck window._finger_worker = stuck window._start_run() window._run_timer.stop() stuck.emit(ui_check._fake_packet(1)) window._on_run_tick() return window, stuck # ------------------------------------------------------------------- the spec itself def test_the_manifest_itself_is_sound(): """A spec with a duplicated id, a dangling pane or an unknown condition is not a spec.""" manifest = elements.manifest() assert manifest.version == 1, "the manifest states a schema version" assert manifest.scope == "apps/studio" assert manifest.path.is_file(), manifest.path assert manifest.problems() == [], manifest.problems() assert manifest.regions(), "no regions declared" assert manifest.panes, "no panes declared" assert manifest.style_tokens(), ( "the stylesheet vocabulary is what the audit subtracts" ) for view in VIEWS: assert manifest.regions(view), f"{view} has no regions" assert manifest.panes_for(view), f"{view} has no panes" assert manifest.elements_for(view), f"{view} has no elements" assert manifest.view_spec(view).purpose, f"{view} has no stated purpose" by_name = manifest.by_object_name() assert by_name, "no element records a stable objectName" assert all(items for items in by_name.values()) assert len(manifest.by_id()) == len(manifest.all_elements()) def test_every_declared_condition_is_used_and_reaches_a_window(make_window): """Conditions are the escape hatch for lazily-built elements, so prove each one opens. A manifest with a condition no window ever satisfies is a manifest that quietly checks less than it appears to, and a condition the loader declares but the manifest never uses is vocabulary that can rot. """ manifest = elements.manifest() used = { element.condition for element in manifest.all_elements() if element.condition } assert used <= set(elements.CONDITIONS), "undeclared condition in the manifest" assert used == set(elements.CONDITIONS), ( f"unused conditions in the loader: {sorted(set(elements.CONDITIONS) - used)}" ) exercised: set[str] = set() for scenario, expected in SCENARIO_CONDITIONS.items(): window = _window(make_window, scenario, "live") result = elements.validate(window, "live") assert result.ok, f"{scenario}:\n{result.summary()}" held = { name for name in elements.CONDITIONS if elements.condition_holds(window, name) } assert held == expected, f"{scenario}: satisfied {sorted(held)}" exercised |= held window.close() window.deleteLater() assert exercised == set(elements.CONDITIONS), ( f"no window satisfies {sorted(set(elements.CONDITIONS) - exercised)}" ) # -------------------------------------------------------------- the elements themselves @pytest.mark.parametrize("view", VIEWS) def test_every_element_of_the_view_exists_is_reachable_and_is_the_recorded_class( make_window, view ): window = _window(make_window, "box", view) try: absent = elements.missing(window, view) assert absent == [], [element.key for element in absent] assert elements.wrong_class(window, view) == [] result = elements.validate(window, view) assert result.wrong_attrs == (), result.summary() assert result.pane_visibility == (), result.summary() # Nothing is silently skipped: the checked and the skipped elements are the manifest's assert result.checked + len(result.skipped) == len( elements.manifest().elements_for(view) ) finally: window.close() @pytest.mark.parametrize("view", VIEWS) def test_the_named_widget_audit_matches_the_manifest(make_window, view): """The drift report is compared with the recorded drift, in both directions. A new named widget changes the report and fails here until the manifest accounts for it (as an element, or as a declared stylesheet token); a recorded name that disappears from the window fails too. The report is deliberately not part of the existence test above. """ window = _window(make_window, "box", view) try: manifest = elements.manifest() on_screen = elements.visible_named_widgets(window) assert on_screen, "the audit would be vacuous: no visible named widget at all" audit = elements.unlisted_named_widgets(window, view) recorded = list(manifest.view_spec(view).unlisted_named_widgets) assert audit == recorded, ( f"{view}: named widgets the manifest does not account for: " f"new={sorted(set(audit) - set(recorded))}, " f"recorded but gone={sorted(set(recorded) - set(audit))}" ) result = elements.validate(window, view) assert result.new_unlisted == () and result.stale_unlisted == () assert result.drift is False finally: window.close() # ------------------------------------------------------------------- the views' panes @pytest.mark.parametrize("view", VIEWS) def test_the_panes_on_screen_are_the_views_own(make_window, view): """The view entry's primary/companion lists are the arrangement *and* what is visible.""" window = _window(make_window, "finger", view) try: spec = elements.manifest().view_spec(view) visible = { pane.id for pane in elements.manifest().panes if (widget := elements.resolve(window, pane.element)) is not None and widget.isVisible() } assert visible == set(spec.panes), ( f"{view}: on screen {sorted(visible)} != declared {sorted(spec.panes)}" ) finally: window.close() # --------------------------------------------------------------- the transport contract def test_the_transport_group_matches_the_live_session_states(make_window): """The defect class: a view switch silently taking the session's reset away. The manifest declares the rail's transport group (``transport``, ``stop``, ``reset``, ``record``); this drives the real session and reads those elements back, so the states the group was fixed to produce are the states the manifest names. """ manifest = elements.manifest() by_id = manifest.by_id() group = ("transport", "stop", "reset", "record") assert set(group) <= set(by_id), ( "the manifest no longer declares the transport group" ) assert {by_id[name].group for name in group} == {"rail"}, ( "the transport group left the rail" ) window, stuck = _running_session(make_window) try: transport = elements.resolve(window, by_id["transport"]) reset = elements.resolve(window, by_id["reset"]) stop = elements.resolve(window, by_id["stop"]) assert transport is window.toolbar.btn_run assert reset is window.toolbar.btn_reset assert window._session_phase == "running", window._session_phase # A running session: the transport reads pause and the reset is available. assert transport.isChecked(), ( "a running session must show the transport engaged" ) assert transport.text() == t("toolbar.pause"), transport.text() assert reset.isEnabled(), "a running session must offer the tracker reset" assert stop.isEnabled(), "a running session must offer stop" # Paused: the transport reads resume, and the device action stays available. transport.setChecked(False) assert window._display_paused is True, "unchecking the transport did not pause" assert transport.text() == t("toolbar.resume_label"), transport.text() assert reset.isEnabled(), ( "pausing took the tracker reset away: the device is still open" ) # The view switch that used to disable it: the session is unchanged throughout. window.brand_bar.btn_calibration.click() QApplication.processEvents() assert window.layout_ctl.view_preset == "calibration" assert reset.isEnabled(), "the calibration view disabled a live session's reset" assert transport.text() == t("toolbar.resume_label"), ( "the view switch changed what the transport says about the session" ) window.brand_bar.btn_live.click() QApplication.processEvents() assert reset.isEnabled(), "the return to the live view lost the session's reset" # Resuming: back to the acquiring state. transport.setChecked(True) assert window._display_paused is False assert transport.text() == t("toolbar.pause"), transport.text() finally: finish_run(window, stuck) window.close() def test_the_transport_of_the_replay_view_is_not_the_device_reset(make_window): """The same controls, the other view: an idle transport and no device to zero.""" window = _window(make_window, "box", "replay") try: by_id = elements.manifest().by_id() transport = elements.resolve(window, by_id["transport"]) reset = elements.resolve(window, by_id["reset"]) assert not transport.isChecked() assert transport.text() == t("toolbar.playback"), transport.text() assert not reset.isEnabled(), ( "reset is offered where there is no tracker to zero" ) finally: window.close()