bookmark - UI Priority 1 Implemented
This commit is contained in:
0
tests/test_web/__init__.py
Normal file
0
tests/test_web/__init__.py
Normal file
83
tests/test_web/test_app.py
Normal file
83
tests/test_web/test_app.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Tests for web app creation."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from impakt.io.mme import MMEReader
|
||||
from impakt.web.app import create_app
|
||||
from impakt.web.state import AppState
|
||||
|
||||
FIXTURE_DATA = Path(__file__).parent.parent / "fixtures" / "sample_mme"
|
||||
MME_DATA = Path(__file__).parent.parent / "mme_data"
|
||||
|
||||
|
||||
class TestAppCreation:
|
||||
def test_create_empty_app(self):
|
||||
app = create_app()
|
||||
assert app is not None
|
||||
assert app.title == "Impakt"
|
||||
|
||||
def test_create_app_with_test_data(self):
|
||||
reader = MMEReader()
|
||||
data = reader.read(FIXTURE_DATA)
|
||||
app = create_app(data)
|
||||
assert app is not None
|
||||
assert "IMPAKT_SYNTH_001" in app.title
|
||||
|
||||
def test_create_app_with_app_state(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
app = create_app(app_state=state)
|
||||
assert app is not None
|
||||
assert "IMPAKT_SYNTH_001" in app.title
|
||||
|
||||
@pytest.mark.skipif(not (MME_DATA / "3239").exists(), reason="Real data not available")
|
||||
def test_create_app_with_real_data(self):
|
||||
state = AppState()
|
||||
state.load_test(MME_DATA / "3239")
|
||||
app = create_app(app_state=state)
|
||||
assert app is not None
|
||||
assert "3239" in app.title
|
||||
|
||||
|
||||
class TestCriteriaAutoCompute:
|
||||
def test_auto_compute_on_synthetic_data(self):
|
||||
from impakt.web.components.criteria import auto_compute_criteria
|
||||
|
||||
reader = MMEReader()
|
||||
data = reader.read(FIXTURE_DATA)
|
||||
|
||||
criteria = auto_compute_criteria(data)
|
||||
# Synthetic data should have head accel -> HIC15
|
||||
assert len(criteria) > 0
|
||||
# Should at least get HIC15 and chest deflection
|
||||
criterion_names = set(criteria.keys())
|
||||
assert "HIC15" in criterion_names or "Chest Deflection" in criterion_names
|
||||
|
||||
@pytest.mark.skipif(not (MME_DATA / "3239").exists(), reason="Real data not available")
|
||||
def test_auto_compute_on_real_data(self):
|
||||
from impakt.web.components.criteria import auto_compute_criteria
|
||||
|
||||
reader = MMEReader()
|
||||
data = reader.read(MME_DATA / "3239")
|
||||
|
||||
criteria = auto_compute_criteria(data)
|
||||
# Real NHTSA data should yield multiple criteria
|
||||
assert len(criteria) >= 3
|
||||
criterion_names = set(criteria.keys())
|
||||
# 3239 has head, chest, neck, femur channels
|
||||
assert "HIC15" in criterion_names
|
||||
|
||||
@pytest.mark.skipif(not (MME_DATA / "3239").exists(), reason="Real data not available")
|
||||
def test_protocol_scoring_on_real_data(self):
|
||||
from impakt.web.components.criteria import auto_compute_criteria, score_protocol
|
||||
|
||||
reader = MMEReader()
|
||||
data = reader.read(MME_DATA / "3239")
|
||||
|
||||
criteria = auto_compute_criteria(data)
|
||||
for protocol in ["euro_ncap", "us_ncap", "iihs"]:
|
||||
result = score_protocol(criteria, protocol)
|
||||
assert result is not None
|
||||
assert len(result.region_scores) > 0
|
||||
140
tests/test_web/test_channel_grid.py
Normal file
140
tests/test_web/test_channel_grid.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""Tests for channel grid component."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from impakt.io.mme import MMEReader
|
||||
from impakt.web.components.channel_grid import (
|
||||
_build_channel_rows,
|
||||
build_selected_channels_badges,
|
||||
filter_rows,
|
||||
)
|
||||
from impakt.web.state import AppState
|
||||
|
||||
FIXTURE_DATA = Path(__file__).parent.parent / "fixtures" / "sample_mme"
|
||||
MME_DATA = Path(__file__).parent.parent / "mme_data"
|
||||
|
||||
|
||||
class TestChannelRows:
|
||||
def test_build_rows_from_synthetic(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
rows = _build_channel_rows(state)
|
||||
assert len(rows) == 26
|
||||
# Each row should have required fields
|
||||
for r in rows:
|
||||
assert "key" in r
|
||||
assert "iso_code" in r
|
||||
assert "unit" in r
|
||||
assert "min" in r
|
||||
assert "max" in r
|
||||
assert "::" in r["key"]
|
||||
|
||||
@pytest.mark.skipif(not (MME_DATA / "3239").exists(), reason="Real data not available")
|
||||
def test_build_rows_from_real_data(self):
|
||||
state = AppState()
|
||||
state.load_test(MME_DATA / "3239")
|
||||
rows = _build_channel_rows(state)
|
||||
assert len(rows) == 133
|
||||
|
||||
def test_multi_test_rows_have_test_id(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
if (MME_DATA / "VW1FGS15").exists():
|
||||
state.load_test(MME_DATA / "VW1FGS15")
|
||||
rows = _build_channel_rows(state)
|
||||
assert len(rows) == 36 # 26 + 10
|
||||
# Multi-test rows should have test_id filled
|
||||
test_ids = {r["test_id"] for r in rows}
|
||||
assert len(test_ids) == 2
|
||||
|
||||
def test_rows_preserve_load_order(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
rows = _build_channel_rows(state)
|
||||
# First channel in fixture should be first row
|
||||
# (this depends on how MMEReader yields channels — sorted by name)
|
||||
iso_codes = [r["iso_code"] for r in rows]
|
||||
assert len(iso_codes) == 26
|
||||
|
||||
|
||||
class TestFilterRows:
|
||||
@pytest.fixture
|
||||
def rows(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
return _build_channel_rows(state)
|
||||
|
||||
def test_no_filter_returns_all(self, rows):
|
||||
result = filter_rows(rows)
|
||||
assert len(result) == len(rows)
|
||||
|
||||
def test_wildcard_filter_head(self, rows):
|
||||
result = filter_rows(rows, pattern="*HEAD*")
|
||||
assert len(result) > 0
|
||||
assert all("HEAD" in r["iso_code"] for r in result)
|
||||
|
||||
def test_wildcard_filter_accel(self, rows):
|
||||
result = filter_rows(rows, pattern="*AC*")
|
||||
assert len(result) > 0
|
||||
assert all("AC" in r["iso_code"] for r in result)
|
||||
|
||||
def test_partial_text_auto_wrapped(self, rows):
|
||||
"""Typing 'HEAD' without wildcards should auto-wrap to *HEAD*."""
|
||||
result = filter_rows(rows, pattern="HEAD")
|
||||
assert len(result) > 0
|
||||
assert all("HEAD" in r["iso_code"] for r in result)
|
||||
|
||||
def test_filter_by_body_facet(self, rows):
|
||||
result = filter_rows(rows, body="Head")
|
||||
assert len(result) > 0
|
||||
assert all(r["body"] == "Head" for r in result)
|
||||
|
||||
def test_filter_by_direction_facet(self, rows):
|
||||
result = filter_rows(rows, direction="X")
|
||||
assert len(result) > 0
|
||||
assert all(r["direction"] == "X" for r in result)
|
||||
|
||||
def test_combined_filter(self, rows):
|
||||
result = filter_rows(rows, pattern="*AC*", direction="X")
|
||||
assert len(result) > 0
|
||||
for r in result:
|
||||
assert "AC" in r["iso_code"]
|
||||
assert r["direction"] == "X"
|
||||
|
||||
def test_no_match_returns_empty(self, rows):
|
||||
result = filter_rows(rows, pattern="NONEXISTENT")
|
||||
assert len(result) == 0
|
||||
|
||||
@pytest.mark.skipif(not (MME_DATA / "3239").exists(), reason="Real data not available")
|
||||
def test_wildcard_filter_real_data(self):
|
||||
state = AppState()
|
||||
state.load_test(MME_DATA / "3239")
|
||||
rows = _build_channel_rows(state)
|
||||
|
||||
# Filter for head channels
|
||||
head = filter_rows(rows, pattern="*HEAD*")
|
||||
assert len(head) > 0
|
||||
|
||||
# Filter for barrier load cells
|
||||
barrier = filter_rows(rows, pattern="B0FBAR*")
|
||||
assert len(barrier) > 0
|
||||
|
||||
# Filter for femur
|
||||
femur = filter_rows(rows, pattern="*FEMR*")
|
||||
assert len(femur) >= 2 # Left and right
|
||||
|
||||
|
||||
class TestSelectedBadges:
|
||||
def test_empty_selection(self):
|
||||
state = AppState()
|
||||
badges = build_selected_channels_badges([], state)
|
||||
assert len(badges) == 1 # "No channels selected"
|
||||
|
||||
def test_with_selection(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
keys = ["IMPAKT_SYNTH_001::11HEAD0000ACXA", "IMPAKT_SYNTH_001::11HEAD0000ACYA"]
|
||||
badges = build_selected_channels_badges(keys, state)
|
||||
assert len(badges) == 2
|
||||
108
tests/test_web/test_state.py
Normal file
108
tests/test_web/test_state.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Tests for web AppState."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from impakt.web.state import AppState
|
||||
|
||||
MME_DATA = Path(__file__).parent.parent / "mme_data"
|
||||
FIXTURE_DATA = Path(__file__).parent.parent / "fixtures" / "sample_mme"
|
||||
|
||||
|
||||
class TestAppState:
|
||||
def test_initially_empty(self):
|
||||
state = AppState()
|
||||
assert state.is_empty
|
||||
assert state.total_channels == 0
|
||||
assert state.primary_test is None
|
||||
|
||||
def test_load_test(self):
|
||||
state = AppState()
|
||||
loaded = state.load_test(FIXTURE_DATA)
|
||||
assert not state.is_empty
|
||||
assert loaded.test_id == "IMPAKT_SYNTH_001"
|
||||
assert loaded.channel_count == 26
|
||||
assert state.primary_test is loaded
|
||||
|
||||
def test_load_multiple_tests(self):
|
||||
state = AppState()
|
||||
t1 = state.load_test(FIXTURE_DATA)
|
||||
|
||||
if (MME_DATA / "VW1FGS15").exists():
|
||||
t2 = state.load_test(MME_DATA / "VW1FGS15")
|
||||
assert len(state.tests) == 2
|
||||
assert state.primary_test is t1 # First loaded is primary
|
||||
assert state.total_channels == 26 + 10
|
||||
|
||||
def test_remove_test(self):
|
||||
state = AppState()
|
||||
loaded = state.load_test(FIXTURE_DATA)
|
||||
state.remove_test(loaded.test_id)
|
||||
assert state.is_empty
|
||||
|
||||
def test_get_channel(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
ch = state.get_channel("IMPAKT_SYNTH_001", "11HEAD0000ACXA")
|
||||
assert ch is not None
|
||||
assert ch.name == "11HEAD0000ACXA"
|
||||
|
||||
def test_get_channel_missing(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
ch = state.get_channel("IMPAKT_SYNTH_001", "NONEXISTENT")
|
||||
assert ch is None
|
||||
|
||||
def test_resolve_channel_with_key(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
ch = state.resolve_channel("IMPAKT_SYNTH_001::11HEAD0000ACXA")
|
||||
assert ch is not None
|
||||
|
||||
def test_resolve_channel_primary_default(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
ch = state.resolve_channel("11HEAD0000ACXA")
|
||||
assert ch is not None
|
||||
|
||||
def test_resolve_channel_with_cfc(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
ch = state.resolve_channel("11HEAD0000ACXA", cfc_class=600)
|
||||
assert ch is not None
|
||||
assert ch.cfc_class == 600
|
||||
|
||||
def test_flat_channel_list(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
items = state.flat_channel_list()
|
||||
assert len(items) == 26
|
||||
assert all("value" in item and "label" in item for item in items)
|
||||
|
||||
def test_build_channel_tree(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
tree = state.build_channel_tree()
|
||||
assert "IMPAKT_SYNTH_001" in tree
|
||||
# Should have hierarchical structure
|
||||
test_tree = tree["IMPAKT_SYNTH_001"]
|
||||
assert len(test_tree) > 0
|
||||
|
||||
|
||||
@pytest.mark.skipif(not (MME_DATA / "3239").exists(), reason="Real MME data not available")
|
||||
class TestAppStateRealData:
|
||||
def test_load_real_mme(self):
|
||||
state = AppState()
|
||||
loaded = state.load_test(MME_DATA / "3239")
|
||||
assert loaded.test_id == "3239"
|
||||
assert loaded.channel_count == 133
|
||||
|
||||
def test_channel_tree_real_data(self):
|
||||
state = AppState()
|
||||
state.load_test(MME_DATA / "3239")
|
||||
tree = state.build_channel_tree()
|
||||
assert "3239" in tree
|
||||
# Should contain "Driver" in some key
|
||||
test_tree = tree["3239"]
|
||||
assert any("Driver" in k for k in test_tree)
|
||||
Reference in New Issue
Block a user