bookmark - UI P2
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
230
tests/test_web/test_p2_features.py
Normal file
230
tests/test_web/test_p2_features.py
Normal file
@@ -0,0 +1,230 @@
|
||||
"""Tests for Priority 2 web UI features.
|
||||
|
||||
Covers: templates, per-channel overrides, corridors, channel values, math expressions.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from impakt.io.mme import MMEReader
|
||||
from impakt.web.state import AppState
|
||||
|
||||
FIXTURE_DATA = Path(__file__).parent.parent / "fixtures" / "sample_mme"
|
||||
MME_DATA = Path(__file__).parent.parent / "mme_data"
|
||||
|
||||
|
||||
class TestTemplateManagement:
|
||||
def test_save_and_apply_template(self, tmp_path):
|
||||
from impakt.template.library import TemplateLibrary
|
||||
|
||||
state = AppState()
|
||||
state._template_library = TemplateLibrary(tmp_path / "templates")
|
||||
state.load_test(FIXTURE_DATA)
|
||||
|
||||
# Save current state as template
|
||||
selected = [
|
||||
"IMPAKT_SYNTH_001::11HEAD0000ACXA",
|
||||
"IMPAKT_SYNTH_001::11HEAD0000ACYA",
|
||||
"IMPAKT_SYNTH_001::11HEAD0000ACZA",
|
||||
]
|
||||
template = state.save_as_template(
|
||||
name="Test Head Analysis",
|
||||
description="Head acceleration channels",
|
||||
selected_keys=selected,
|
||||
cfc_value="1000",
|
||||
x1=0.0,
|
||||
x2=0.05,
|
||||
)
|
||||
|
||||
assert template.name == "Test Head Analysis"
|
||||
assert template.default_cfc == 1000
|
||||
assert len(template.plots) == 1
|
||||
assert len(template.plots[0].channel_patterns) == 3
|
||||
|
||||
# Apply the template
|
||||
resolved_keys, transforms = state.apply_template("test_head_analysis")
|
||||
assert len(resolved_keys) == 3
|
||||
assert transforms["cfc"] == "1000"
|
||||
assert state.active_template is not None
|
||||
assert state.active_template.name == "Test Head Analysis"
|
||||
|
||||
def test_template_names(self, tmp_path):
|
||||
from impakt.template.library import TemplateLibrary
|
||||
|
||||
state = AppState()
|
||||
state._template_library = TemplateLibrary(tmp_path / "templates")
|
||||
assert state.template_names == []
|
||||
|
||||
state.load_test(FIXTURE_DATA)
|
||||
state.save_as_template(
|
||||
name="Test",
|
||||
description="",
|
||||
selected_keys=["IMPAKT_SYNTH_001::11HEAD0000ACXA"],
|
||||
cfc_value="none",
|
||||
x1=None,
|
||||
x2=None,
|
||||
)
|
||||
assert "test" in state.template_names
|
||||
|
||||
|
||||
class TestPerChannelOverrides:
|
||||
def test_set_and_read_override(self):
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
|
||||
key = "IMPAKT_SYNTH_001::11HEAD0000ACXA"
|
||||
state.channel_overrides[key] = {"cfc": "600"}
|
||||
|
||||
assert state.channel_overrides[key]["cfc"] == "600"
|
||||
|
||||
def test_override_clears(self):
|
||||
state = AppState()
|
||||
state.channel_overrides["key"] = {"cfc": "180"}
|
||||
state.channel_overrides.pop("key", None)
|
||||
assert "key" not in state.channel_overrides
|
||||
|
||||
|
||||
class TestCorridors:
|
||||
def test_add_corridor(self):
|
||||
state = AppState()
|
||||
corridor = {
|
||||
"name": "Test Corridor",
|
||||
"time": [0.0, 0.01, 0.02, 0.03],
|
||||
"lower": [-10, -20, -20, -10],
|
||||
"upper": [10, 20, 20, 10],
|
||||
"visible": True,
|
||||
}
|
||||
state.corridors.append(corridor)
|
||||
assert len(state.corridors) == 1
|
||||
assert state.corridors[0]["name"] == "Test Corridor"
|
||||
|
||||
|
||||
class TestChannelValues:
|
||||
def test_build_channel_values_data(self):
|
||||
from impakt.web.components.channel_values import build_channel_values_data
|
||||
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
|
||||
channels = []
|
||||
for name in ["11HEAD0000ACXA", "11HEAD0000ACYA"]:
|
||||
ch = state.get_channel("IMPAKT_SYNTH_001", name)
|
||||
if ch:
|
||||
channels.append((ch.code.short_label, ch))
|
||||
|
||||
rows = build_channel_values_data(channels, hover_x=0.05, x1=0.0, x2=0.1)
|
||||
assert len(rows) == 2
|
||||
|
||||
for row in rows:
|
||||
assert "ch_num" in row
|
||||
assert "iso_code" in row
|
||||
assert "description" in row
|
||||
assert "unit" in row
|
||||
assert "min" in row
|
||||
assert "min_time" in row
|
||||
assert "max" in row
|
||||
assert "max_time" in row
|
||||
assert "x1" in row
|
||||
assert "x2" in row
|
||||
assert "cursor" in row
|
||||
|
||||
# Channel numbers should be sequential starting at 1
|
||||
assert rows[0]["ch_num"] == 1
|
||||
assert rows[1]["ch_num"] == 2
|
||||
|
||||
# Values should be parseable as floats
|
||||
for row in rows:
|
||||
float(row["min"])
|
||||
float(row["max"])
|
||||
float(row["x1"])
|
||||
float(row["x2"])
|
||||
float(row["cursor"])
|
||||
|
||||
def test_channel_values_no_hover(self):
|
||||
from impakt.web.components.channel_values import build_channel_values_data
|
||||
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
|
||||
ch = state.get_channel("IMPAKT_SYNTH_001", "11HEAD0000ACXA")
|
||||
rows = build_channel_values_data([("Head X", ch)], hover_x=None, x1=0.0, x2=0.1)
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["cursor"] == "" # No hover = empty cursor column
|
||||
|
||||
@pytest.mark.skipif(not (MME_DATA / "3239").exists(), reason="Real data not available")
|
||||
def test_channel_values_real_data(self):
|
||||
from impakt.web.components.channel_values import build_channel_values_data
|
||||
|
||||
state = AppState()
|
||||
state.load_test(MME_DATA / "3239")
|
||||
|
||||
ch = state.get_channel("3239", "11HEAD0000H3ACXP")
|
||||
assert ch is not None
|
||||
|
||||
rows = build_channel_values_data([("Head Accel X", ch)], hover_x=0.05, x1=0.0, x2=0.05)
|
||||
assert len(rows) == 1
|
||||
# Min should be significant (frontal crash head X accel)
|
||||
assert abs(float(rows[0]["min"])) > 100
|
||||
|
||||
|
||||
class TestMathExpression:
|
||||
def test_math_expr_in_state(self):
|
||||
from impakt.transform.math_expr import math_expr
|
||||
|
||||
state = AppState()
|
||||
state.load_test(FIXTURE_DATA)
|
||||
|
||||
ch_x = state.get_channel("IMPAKT_SYNTH_001", "11HEAD0000ACXA")
|
||||
ch_z = state.get_channel("IMPAKT_SYNTH_001", "11HEAD0000ACZA")
|
||||
assert ch_x is not None and ch_z is not None
|
||||
|
||||
result = math_expr(
|
||||
expression="sqrt(a**2 + b**2)",
|
||||
channels={"a": ch_x, "b": ch_z},
|
||||
name="head_xz_resultant",
|
||||
unit="g",
|
||||
)
|
||||
|
||||
assert result.name == "head_xz_resultant"
|
||||
assert result.unit == "g"
|
||||
assert result.peak > 0
|
||||
assert len(result.data) == len(ch_x.data)
|
||||
|
||||
# Store in primary test
|
||||
primary = state.primary_test
|
||||
primary.data._channels["head_xz_resultant"] = result
|
||||
|
||||
# Should be retrievable
|
||||
retrieved = state.get_channel("IMPAKT_SYNTH_001", "head_xz_resultant")
|
||||
assert retrieved is not None
|
||||
assert retrieved.peak == result.peak
|
||||
|
||||
|
||||
class TestSessionAutoSave:
|
||||
def test_save_and_load_session(self, tmp_path):
|
||||
# Create a minimal MME fixture in tmp_path
|
||||
import shutil
|
||||
|
||||
test_dir = tmp_path / "test_session"
|
||||
shutil.copytree(FIXTURE_DATA, test_dir)
|
||||
|
||||
state = AppState()
|
||||
state.load_test(test_dir)
|
||||
|
||||
# Save session
|
||||
selected = ["IMPAKT_SYNTH_001::11HEAD0000ACXA"]
|
||||
state.save_session(selected, "600")
|
||||
|
||||
# Verify .impakt directory was created
|
||||
impakt_dir = test_dir / ".impakt"
|
||||
assert impakt_dir.exists()
|
||||
assert (impakt_dir / "session.yaml").exists()
|
||||
|
||||
# Load session state
|
||||
session_data = state.load_session_state()
|
||||
assert session_data is not None
|
||||
assert session_data["cfc"] == "600"
|
||||
assert session_data["selected_channels"] == selected
|
||||
Reference in New Issue
Block a user