Show only the bundle's name in the sensor configuration row
The row carried the origin, the calibration selection and the SDK's verdict, which a 30 px line cannot explain; it is now the descriptor's display_name, else the folder name, and the sensor dialog states the rest under the selector — name and folder, profile, shape, root, verdict, calibration and every finding — with a suffix only where two rows would otherwise read alike. Verified with the studio suite, the catalog tests in shared/tests, ui_check and the lint gate.
This commit is contained in:
@@ -10,14 +10,17 @@ selected, and two bundles sharing one ``profile_id``.
|
||||
The tree is built here, in the test's own temporary directory: a committed fixture would be a
|
||||
second copy of the bundle format that could drift from the real one without anyone noticing.
|
||||
|
||||
The labels are asserted here too, because they are the catalog's whole operator-facing
|
||||
contract: what a configuration is, where it came from and what it will give — never a
|
||||
``profile_id``, an ``ok``/``degraded``/``failed`` verdict, or the word ``legacy``.
|
||||
The rows are asserted here too, because they are the catalog's whole operator-facing contract: a
|
||||
row is the bundle's name — the descriptor's ``display_name``, else the folder name — and never a
|
||||
``profile_id``, an ``ok``/``degraded``/``failed`` verdict, the origin word, the calibration
|
||||
phrasing or the word ``legacy``. A suffix is added in exactly one case, two visible entries that
|
||||
would otherwise read alike, and the suffix is asserted to be the one that tells them apart.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -86,6 +89,11 @@ def _write(path: Path, payload: object) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
def _WORD_RE(word: str) -> re.Pattern[str]:
|
||||
"""``word`` matched whole: a folder may legitimately contain its letters (``broken-manifest``)."""
|
||||
return re.compile(rf"\b{re.escape(word)}\b")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def trees(tmp_path):
|
||||
"""One shipped root and one user root covering every shape the catalog must handle."""
|
||||
@@ -256,31 +264,77 @@ def test_an_entry_the_sdk_rejects_is_never_reported_as_startable(trees):
|
||||
assert (entry.start_blocked_reason is None) == expected, entry.ref
|
||||
|
||||
|
||||
def test_the_label_states_what_it_is_where_it_came_from_and_what_it_gives(trees):
|
||||
entry = trees.by_ref("user/mini-lab")
|
||||
def test_the_row_is_the_bundles_name_and_nothing_else(trees):
|
||||
"""The picker row stopped carrying the origin, the calibration and the verdict.
|
||||
|
||||
A legacy bundle with no collision is named by the SDK's built-in table for its folder; that
|
||||
name, and nothing after it, is the row. What moved out of the row is asserted where it has
|
||||
to be readable instead: the dialog's details surface, in ``apps/studio/tests``.
|
||||
"""
|
||||
entry = trees.by_ref("shipped/box")
|
||||
assert entry is not None
|
||||
assert entry.label() == "Mini Lab · User · No calibration selected — raw force"
|
||||
assert trees.row_label(entry) == "Box"
|
||||
assert trees.row_label(entry) == entry.name
|
||||
|
||||
|
||||
def test_a_startable_label_carries_no_identity_and_no_severity_word(trees):
|
||||
"""What an operator chooses between is products and outcomes, not internal names."""
|
||||
def test_no_row_carries_identity_origin_calibration_or_a_severity_word(trees):
|
||||
"""What an operator chooses between is products, not internal names or severities.
|
||||
|
||||
Checked two ways: through the shape of the row — the name, or the name with a collision
|
||||
suffix and nothing else, so nothing can have been appended — and as whole words, because a
|
||||
folder called ``broken-manifest`` legitimately contains the letters of ``ok``.
|
||||
"""
|
||||
for entry in trees.entries:
|
||||
if not entry.valid:
|
||||
continue
|
||||
label = entry.label()
|
||||
assert entry.profile_id not in label, label
|
||||
for word in ("ok", "degraded", "failed", "legacy"):
|
||||
assert word not in label, f"{word!r} reached the row: {label}"
|
||||
row = trees.row_label(entry)
|
||||
assert row == entry.name or row.startswith(f"{entry.name} ("), row
|
||||
if entry.profile_id:
|
||||
assert entry.profile_id not in row, row
|
||||
for word in ("ok", "degraded", "failed", "legacy", "Shipped", "Calibration"):
|
||||
assert _WORD_RE(word).search(row) is None, f"{word!r} reached the row: {row}"
|
||||
|
||||
|
||||
def test_no_calibration_selected_reads_as_raw_force_not_as_a_defect(trees):
|
||||
entry = trees.by_ref("user/mini-lab")
|
||||
assert entry is not None
|
||||
# The SDK reports the absent calibration as a warning, which is the state the row used to
|
||||
# read as "degraded" — a severity word that never said "raw force" or "not selected".
|
||||
assert entry.state == "degraded"
|
||||
assert "No calibration selected — raw force" in entry.label()
|
||||
assert entry.selected_calibration == ""
|
||||
def test_two_roots_offering_one_name_are_told_apart_by_origin(tmp_path):
|
||||
"""``shipped/box`` beside ``sensors-user/box``: two entries, two tellable rows."""
|
||||
shipped = tmp_path / "shipped"
|
||||
user = tmp_path / "user"
|
||||
for root in (shipped, user):
|
||||
_write(root / "box" / "sensor.json", _config(stitcher=True))
|
||||
|
||||
catalog = scan_sensor_catalog(shipped_root=shipped, user_root=user)
|
||||
first = catalog.by_ref("shipped/box")
|
||||
second = catalog.by_ref("user/box")
|
||||
assert first is not None and second is not None
|
||||
assert first.name == second.name == "Box", "the premise of a collision"
|
||||
assert catalog.row_label(first) == "Box (shipped)"
|
||||
assert catalog.row_label(second) == "Box (user)"
|
||||
# Neither is shadowed: both are offered by the family the picker lists them under.
|
||||
offered = [entry.ref for entry in catalog.entries_for_family("box")]
|
||||
assert offered == ["shipped/box", "user/box"]
|
||||
|
||||
|
||||
def test_one_root_declaring_one_name_twice_is_told_apart_by_folder(trees):
|
||||
"""The origin cannot separate two entries of one root, so the folder does.
|
||||
|
||||
``mini-lab`` and ``mini-cal`` both declare ``Mini Lab``: one name, one root, two bundles. A
|
||||
row that reads the same as another row in the same list would be a silent collapse, so each
|
||||
carries the folder — the identity that actually differs.
|
||||
"""
|
||||
mini_lab = trees.row_label(trees.by_ref("user/mini-lab"))
|
||||
mini_cal = trees.row_label(trees.by_ref("user/mini-cal"))
|
||||
assert mini_lab == "Mini Lab (mini-lab)"
|
||||
assert mini_cal == "Mini Lab (mini-cal)"
|
||||
assert mini_lab != mini_cal
|
||||
|
||||
|
||||
def test_a_name_two_entries_share_is_suffixed_without_touching_the_other_families(trees):
|
||||
"""Only the entries that collide carry a suffix; the picker is not made noisy for them."""
|
||||
seen = {entry.key: trees.row_label(entry) for entry in trees.entries}
|
||||
assert seen[trees.by_ref("user/missing-assets").key] == "Finger R01 (map missing)"
|
||||
assert seen[trees.by_ref("shipped/box").key] == "Box"
|
||||
# Every row of one family group is unique, which is the invariant the suffix exists for.
|
||||
for family in {entry.family for entry in trees.entries}:
|
||||
rows = [trees.row_label(entry) for entry in trees.entries_for_family(family)]
|
||||
assert len(rows) == len(set(rows)), (family, rows)
|
||||
|
||||
|
||||
def test_a_selected_calibration_reads_as_a_selection(trees):
|
||||
@@ -289,24 +343,44 @@ def test_a_selected_calibration_reads_as_a_selection(trees):
|
||||
assert entry.state == "ok", entry.reason
|
||||
assert entry.selected_calibration == "force_calibration.json"
|
||||
# Named, and said as something selected — never as a property of the bundle ...
|
||||
assert entry.label() == "Mini Lab · User · Calibration: force_calibration.json"
|
||||
assert entry.calibration_selection == "force_calibration.json"
|
||||
# ... and the "ok" the SDK calls that is not a word an operator needs to read.
|
||||
assert "ok" not in entry.label()
|
||||
assert "ok" not in trees.row_label(entry)
|
||||
|
||||
|
||||
def test_an_unstartable_label_says_cannot_start_with_the_sdks_own_reason(trees):
|
||||
def test_no_calibration_selected_reads_as_raw_force_not_as_a_defect(trees):
|
||||
entry = trees.by_ref("user/mini-lab")
|
||||
assert entry is not None
|
||||
# The SDK reports the absent calibration as a warning, which the row used to read as
|
||||
# "degraded" — a severity word that never said "raw force" or "not selected".
|
||||
assert entry.state == "degraded"
|
||||
assert entry.selected_calibration == ""
|
||||
assert entry.calibration_selection == "No calibration selected — raw force"
|
||||
|
||||
|
||||
def test_an_unstartable_entry_keeps_its_name_and_its_reason(trees):
|
||||
"""The row is the name; the reason the entry cannot start is still the SDK's own."""
|
||||
entry = trees.by_ref("user/missing-assets")
|
||||
assert entry is not None and not entry.valid
|
||||
assert entry.label() == f"Finger R01 (map missing) · Cannot start: {entry.reason}"
|
||||
assert "unroll_map" in entry.label()
|
||||
assert trees.row_label(entry) == "Finger R01 (map missing)"
|
||||
assert entry.start_blocked_reason == entry.reason
|
||||
assert "unroll_map" in entry.start_blocked_reason
|
||||
# The reason quotes the errors it was built from, so the sentence forms are the ones a
|
||||
# finding that is *not* already in it still needs — here, the calibration warning.
|
||||
assert entry.warning_sentences()
|
||||
assert entry.error_sentences() == ()
|
||||
|
||||
|
||||
def test_a_duplicate_label_names_the_twin(trees):
|
||||
def test_a_duplicate_keeps_its_name_and_names_its_twin(trees):
|
||||
first = trees.by_ref("user/dup-a")
|
||||
second = trees.by_ref("user/dup-b")
|
||||
assert first is not None and second is not None
|
||||
assert first.label() == "Mini Lab · User · Also provided by user/dup-b"
|
||||
assert second.label() == "Mini Lab · User · Also provided by user/dup-a"
|
||||
assert first.duplicates == ("user/dup-b",)
|
||||
assert second.duplicates == ("user/dup-a",)
|
||||
assert trees.row_label(first) == "Mini Lab (dup-a)"
|
||||
assert trees.row_label(second) == "Mini Lab (dup-b)"
|
||||
assert "user/dup-b" in first.start_blocked_reason
|
||||
assert "user/dup-a" in second.start_blocked_reason
|
||||
|
||||
|
||||
def test_the_tooltip_keeps_the_identity_the_shape_the_state_and_the_findings(trees):
|
||||
@@ -315,10 +389,12 @@ def test_the_tooltip_keeps_the_identity_the_shape_the_state_and_the_findings(tre
|
||||
assert entry is not None
|
||||
tooltip = entry.tooltip()
|
||||
assert entry.profile_id in tooltip
|
||||
assert entry.folder in tooltip, "the row may be a display name, so the folder must be here"
|
||||
assert str(entry.bundle_path) in tooltip
|
||||
assert "descriptor bundle" in tooltip
|
||||
assert f"SDK validation: {entry.state}" in tooltip
|
||||
assert "processing can start" in tooltip, "the verdict word is left unexplained"
|
||||
assert "calibration: none selected — raw force" in tooltip
|
||||
assert f"calibration selection: {entry.calibration_selection}" in tooltip
|
||||
# A finding is a sentence: the code is dropped, the capability it names is not.
|
||||
assert "calibration.absent" not in tooltip
|
||||
assert "calibrated force is unavailable; raw force is available" in tooltip
|
||||
|
||||
Reference in New Issue
Block a user