"""TDD for spaceutil.plot.build_umap_figure.""" from __future__ import annotations def test_returns_plotly_figure(synthetic_cards): import plotly.graph_objects as go from spaceutil.plot import build_umap_figure fig = build_umap_figure(synthetic_cards) assert isinstance(fig, go.Figure) def test_base_trace_only_without_highlights(synthetic_cards): from spaceutil.plot import build_umap_figure fig = build_umap_figure(synthetic_cards, highlight_indices=None) assert len(fig.data) == 1 def test_overlay_trace_added_for_highlights(synthetic_cards): from spaceutil.plot import build_umap_figure fig = build_umap_figure(synthetic_cards, highlight_indices=[0, 2, 5]) assert len(fig.data) == 2 def test_empty_highlights_treated_as_none(synthetic_cards): from spaceutil.plot import build_umap_figure fig = build_umap_figure(synthetic_cards, highlight_indices=[]) assert len(fig.data) == 1 def test_base_trace_customdata_carries_corpus_index(synthetic_cards): """Click events need to round-trip the corpus index from customdata, so we can resolve any clicked point back to a corpus row regardless of which trace it landed on.""" from spaceutil.plot import build_umap_figure fig = build_umap_figure(synthetic_cards) base = fig.data[0] assert base.customdata is not None assert len(base.customdata) == len(synthetic_cards) first_row = base.customdata[0] # customdata rows may be array-like; first element must be the index. assert int(first_row[0]) == 0 assert int(base.customdata[5][0]) == 5 def test_overlay_trace_customdata_length_matches_highlights(synthetic_cards): from spaceutil.plot import build_umap_figure indices = [1, 3, 7, 11] fig = build_umap_figure(synthetic_cards, highlight_indices=indices) overlay = fig.data[1] assert overlay.customdata is not None assert len(overlay.customdata) == len(indices) def test_hover_text_includes_card_name(synthetic_cards): from spaceutil.plot import build_umap_figure fig = build_umap_figure(synthetic_cards) base = fig.data[0] assert base.text is not None assert len(base.text) == len(synthetic_cards) assert synthetic_cards[0]["name"] in base.text[0] def test_cost_curve_figure_with_hits(): import plotly.graph_objects as go from spaceutil.plot import build_cost_curve_figure from spaceutil.synergy import SynergyHit hits = [ SynergyHit(1, "A", "A", 0.9, 0.0, 0.9, False, "Character", ["Red"], 3, "OP01"), SynergyHit(2, "B", "B", 0.8, 0.0, 0.8, False, "Character", ["Red"], 3, "OP01"), SynergyHit(3, "C", "C", 0.7, 0.0, 0.7, False, "Event", ["Red"], 7, "OP01"), ] fig = build_cost_curve_figure(hits) assert isinstance(fig, go.Figure) assert len(fig.data) == 1 bar = fig.data[0] # Two distinct cost buckets: 3 (count 2) and 7 (count 1) assert list(bar.x) == ["3", "7"] assert list(bar.y) == [2, 1] def test_cost_curve_figure_empty_shows_placeholder(): import plotly.graph_objects as go from spaceutil.plot import build_cost_curve_figure fig = build_cost_curve_figure([]) assert isinstance(fig, go.Figure) # Empty case has no bar trace assert len(fig.data) == 0 # But carries an annotation assert fig.layout.annotations is not None assert any("pick a leader" in a.text.lower() for a in fig.layout.annotations)