"""Unit-system label table tests.""" from __future__ import annotations import pytest from otko.core import UnitSystem, labels_for from otko.core.units import ( QUANTITIES, display_factor, display_for, display_label, display_with_alias, format_display, format_rotation, native_label, to_display, ) def test_metric_labels() -> None: lab = labels_for(UnitSystem.METRIC) assert lab.length == "m" assert lab.force == "N" assert lab.moment == "N·m" assert lab.stress == "Pa" assert lab.curvature == "1/m" def test_imperial_labels() -> None: lab = labels_for(UnitSystem.IMPERIAL) assert lab.length == "in" assert lab.force == "kip" assert lab.moment == "kip·in" assert lab.stress == "ksi" assert lab.curvature == "1/in" def test_only_two_unit_systems_remain() -> None: """The status-bar dropdown offers Metric and Imperial only.""" assert {u.name for u in UnitSystem} == {"METRIC", "IMPERIAL"} def test_legacy_unit_strings_migrate_on_load() -> None: """Old .osmodel files (4-system era) load onto Metric/Imperial.""" from otko.core import ProjectMeta assert ProjectMeta.model_validate({"units": "SI (m, N, kg, s, Pa)"}).units is UnitSystem.METRIC assert ProjectMeta.model_validate({"units": "SI (mm, N, t, s, MPa)"}).units is UnitSystem.METRIC assert ( ProjectMeta.model_validate({"units": "US (ft, kip, slug, s, ksf)"}).units is UnitSystem.IMPERIAL ) assert ( ProjectMeta.model_validate({"units": "US (in, kip, kip·s²/in, s, ksi)"}).units is UnitSystem.IMPERIAL ) def test_labels_for_covers_every_unit_system() -> None: """Every UnitSystem enum value must have a matching label bundle.""" for us in UnitSystem: lab = labels_for(us) # sanity: at least length / force populated. assert lab.length and lab.force def test_display_table_covers_taxonomy_per_system() -> None: """Every system exposes every taxonomy quantity (§5 key set).""" for us in UnitSystem: for q in QUANTITIES: label, factor = display_for(us, q) assert label assert factor > 0 def test_display_identity_for_imperial() -> None: """Imperial stores = displays (no factors).""" for q in QUANTITIES: assert display_factor(UnitSystem.IMPERIAL, q) == 1.0 def test_display_spot_factors() -> None: """Spot-check Metric factors: N→kN, Pa→MPa, m→mm.""" assert display_for(UnitSystem.METRIC, "force") == ("kN", 1e-3) assert display_for(UnitSystem.METRIC, "stress") == ("MPa", 1e-6) assert display_for(UnitSystem.METRIC, "displacement") == ("mm", 1000.0) assert display_for(UnitSystem.METRIC, "section_dim") == ("mm", 1000.0) assert display_for(UnitSystem.METRIC, "area") == ("mm²", 1e6) assert display_for(UnitSystem.METRIC, "inertia") == ("mm⁴", 1e12) assert display_for(UnitSystem.IMPERIAL, "stress") == ("ksi", 1.0) def test_rotation_always_factor_one() -> None: """Rotation stays rad everywhere; ° is formatting only.""" for us in UnitSystem: assert display_for(us, "rotation") == ("rad", 1.0) assert format_rotation(3.14159265).startswith("3.142") assert "°" in format_rotation(3.14159265, use_deg=True) def test_dimensionless_absent_from_table() -> None: """Strain/drift/damping never take factors — KeyError by design.""" for us in UnitSystem: for q in ("strain", "drift", "damping_ratio", "mass_participation"): with pytest.raises(KeyError): display_for(us, q) def test_to_display_applies_factor_on_read() -> None: """1 m → 1000 mm; Imperial identity; values never written back.""" assert to_display(1.0, UnitSystem.METRIC, "displacement") == pytest.approx(1000.0) assert to_display(1.0, UnitSystem.IMPERIAL, "displacement") == pytest.approx(1.0) assert to_display(2.0, UnitSystem.METRIC, "force") == pytest.approx(0.002) assert format_display(1.0, UnitSystem.METRIC, "displacement") == "1000 mm" assert display_label(UnitSystem.IMPERIAL, "moment") == "kip·in" def test_native_labels_match_stored_units() -> None: """Labels next to native values must be native units, not prefixes.""" assert native_label(UnitSystem.METRIC, "force") == "N" assert native_label(UnitSystem.METRIC, "stress") == "Pa" assert native_label(UnitSystem.METRIC, "displacement") == "m" assert native_label(UnitSystem.METRIC, "distributed_load") == "N/m" assert native_label(UnitSystem.METRIC, "area") == "m²" assert native_label(UnitSystem.METRIC, "inertia") == "m⁴" assert native_label(UnitSystem.IMPERIAL, "force") == "kip" assert native_label(UnitSystem.IMPERIAL, "stress") == "ksi" assert native_label(UnitSystem.IMPERIAL, "length") == "in" for us in UnitSystem: for q in QUANTITIES: assert native_label(us, q) def test_small_value_lbf_alias() -> None: """0.004 kip renders as 4.0 lbf; large values stay in kip.""" val, label = display_with_alias(0.004, UnitSystem.IMPERIAL, "force") assert label == "lbf" assert val == pytest.approx(4.0) val_big, label_big = display_with_alias(2.0, UnitSystem.IMPERIAL, "force") assert label_big == "kip" assert val_big == pytest.approx(2.0)