mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-17 08:00:13 +01:00
feat: add supplemental theme catalog tooling, additional theme selection, and custom theme selection
This commit is contained in:
parent
3a1b011dbc
commit
9428e09cef
39 changed files with 3643 additions and 198 deletions
92
code/tests/test_theme_matcher.py
Normal file
92
code/tests/test_theme_matcher.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from code.deck_builder.theme_catalog_loader import ThemeCatalogEntry
|
||||
from code.deck_builder.theme_matcher import (
|
||||
ACCEPT_MATCH_THRESHOLD,
|
||||
SUGGEST_MATCH_THRESHOLD,
|
||||
ThemeMatcher,
|
||||
normalize_theme,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def sample_entries() -> list[ThemeCatalogEntry]:
|
||||
themes = [
|
||||
"Aristocrats",
|
||||
"Sacrifice Matters",
|
||||
"Life Gain",
|
||||
"Token Swarm",
|
||||
"Control",
|
||||
"Superfriends",
|
||||
"Spellslinger",
|
||||
"Artifact Tokens",
|
||||
"Treasure Storm",
|
||||
"Graveyard Loops",
|
||||
]
|
||||
return [ThemeCatalogEntry(theme=theme, commander_count=0, card_count=0) for theme in themes]
|
||||
|
||||
|
||||
def test_normalize_theme_collapses_spaces() -> None:
|
||||
assert normalize_theme(" Life Gain \t") == "life gain"
|
||||
|
||||
|
||||
def test_exact_match_case_insensitive(sample_entries: list[ThemeCatalogEntry]) -> None:
|
||||
matcher = ThemeMatcher(sample_entries)
|
||||
result = matcher.resolve("aristocrats")
|
||||
assert result.matched_theme == "Aristocrats"
|
||||
assert result.score == pytest.approx(100.0)
|
||||
assert result.reason == "high_confidence"
|
||||
|
||||
|
||||
def test_minor_typo_accepts_with_high_score(sample_entries: list[ThemeCatalogEntry]) -> None:
|
||||
matcher = ThemeMatcher(sample_entries)
|
||||
result = matcher.resolve("aristrocrats")
|
||||
assert result.matched_theme == "Aristocrats"
|
||||
assert result.score >= ACCEPT_MATCH_THRESHOLD
|
||||
assert result.reason in {"high_confidence", "accepted_confidence"}
|
||||
|
||||
|
||||
def test_multi_typo_only_suggests(sample_entries: list[ThemeCatalogEntry]) -> None:
|
||||
matcher = ThemeMatcher(sample_entries)
|
||||
result = matcher.resolve("arzstrcrats")
|
||||
assert result.matched_theme is None
|
||||
assert result.score >= SUGGEST_MATCH_THRESHOLD
|
||||
assert result.reason == "suggestions"
|
||||
assert any(s.theme == "Aristocrats" for s in result.suggestions)
|
||||
|
||||
|
||||
def test_no_match_returns_empty(sample_entries: list[ThemeCatalogEntry]) -> None:
|
||||
matcher = ThemeMatcher(sample_entries)
|
||||
result = matcher.resolve("planeship")
|
||||
assert result.matched_theme is None
|
||||
assert result.suggestions == []
|
||||
assert result.reason in {"no_candidates", "no_match"}
|
||||
|
||||
|
||||
def test_short_input_requires_exact(sample_entries: list[ThemeCatalogEntry]) -> None:
|
||||
matcher = ThemeMatcher(sample_entries)
|
||||
result = matcher.resolve("ar")
|
||||
assert result.matched_theme is None
|
||||
assert result.reason == "input_too_short"
|
||||
|
||||
result_exact = matcher.resolve("lo")
|
||||
assert result_exact.matched_theme is None
|
||||
|
||||
|
||||
def test_resolution_speed(sample_entries: list[ThemeCatalogEntry]) -> None:
|
||||
many_entries = [
|
||||
ThemeCatalogEntry(theme=f"Theme {i}", commander_count=0, card_count=0) for i in range(400)
|
||||
]
|
||||
matcher = ThemeMatcher(many_entries)
|
||||
matcher.resolve("theme 42")
|
||||
|
||||
start = time.perf_counter()
|
||||
for _ in range(20):
|
||||
matcher.resolve("theme 123")
|
||||
duration = time.perf_counter() - start
|
||||
# Observed ~0.03s per resolution (<=0.65s for 20 resolves) on dev machine (2025-10-02).
|
||||
assert duration < 0.7
|
||||
Loading…
Add table
Add a link
Reference in a new issue