Files
Aeth3randClaude Opus 5.5 b3325e8ecc Merge views into Match, expand history, and polish the UI
- Match page now holds players/match info, turn stats with a turn
  picker, and the deck lists (My deck · Preview · Opponent); the Deck
  and Turn tabs are gone. Section heights are draggable and remembered.
- History: sortable, resizable columns; deck and commander columns;
  right-click to show only or hide games with a value, with filter
  chips; stats tiles (win rate overall, last 10/100, last 7 days,
  play/draw, average length and turns) that follow the filters.
  Commanders are backfilled for games recorded before they were stored.
- Deck panel shows copies per zone against the deck total (e.g. 2/5).
- Preview can be cleared with ✕, a click on the image, or Esc.
- Customize menu stays open while toggling; Arrange covers deck
  sections and turn stats together.
- Fix squeezed nav buttons and the header pushing controls off-screen.
- Fix known opponent cards being double-counted when also revealed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 12:34:02 -04:00

101 lines
3.6 KiB
Python

from mtga_assistant.history import HistoryRecorder, HistoryStore, decode, encode, import_log, replay_game
from mtga_assistant.logparse import LogParser
from test_gamestate import FIXTURE, ME
def parse(path):
p = LogParser()
with open(path, encoding="utf-8") as f:
return [ev for line in f for ev in p.feed(line)]
def test_events_round_trip():
for ev in parse(FIXTURE):
enc = encode(ev)
if enc is None:
continue
back = decode(enc)
assert type(back) is type(ev)
def test_import_records_each_game_once(tmp_path):
store = HistoryStore(tmp_path / "h.db")
assert import_log(FIXTURE, store, None) == 1
assert import_log(FIXTURE, store, None) == 0 # already recorded
games = store.games()
assert [g.game_number for g in games] == [2, 1] # newest first
g1 = games[1]
assert g1.opponent == "Opponent Player"
assert g1.result == "Won" and games[0].result == "Won"
assert g1.best_of == 3
assert g1.on_play == "me"
assert g1.deck_name == "Cruel Tutelage"
assert g1.replayable
def test_replay_stops_at_the_requested_game(tmp_path):
store = HistoryStore(tmp_path / "h.db")
import_log(FIXTURE, store, None)
events = store.events(store.games()[0].match_id)
t1 = replay_game(events, 1, None)
assert t1.game.game_number == 1
assert t1.game.on_play == ME
assert t1.game_results == ["Won"]
t2 = replay_game(events, 2, None)
assert t2.game.game_number == 2
assert t2.game_results == ["Won", "Won"]
assert t2.games_on_draw() == {2: 1}
def test_events_pruned_beyond_cap_but_summaries_kept(tmp_path):
store = HistoryStore(tmp_path / "h.db", max_events_mb=0)
import_log(FIXTURE, store, None)
games = store.games()
assert len(games) == 2 and not any(g.replayable for g in games)
assert store.events(games[0].match_id) == []
def test_recorder_ignores_unfinished_match(tmp_path):
from mtga_assistant.gamestate import MatchTracker
store = HistoryStore(tmp_path / "h.db")
rec, t = HistoryRecorder(store), MatchTracker()
events = parse(FIXTURE)
for ev in events[:-3]: # stop before the match completes
t.handle(ev)
rec.observe(ev, t)
assert store.games() == []
def test_old_database_is_upgraded_and_backfilled(tmp_path):
import sqlite3
path = tmp_path / "old.db"
store = HistoryStore(path)
import_log(FIXTURE, store, None)
# Simulate a database from before commanders were stored.
conn = sqlite3.connect(path)
conn.execute("UPDATE games SET my_commander = NULL, opp_commander = NULL")
conn.commit()
conn.close()
store = HistoryStore(path)
assert all(g.my_commander is None for g in store.games())
assert store.backfill_commanders(None) == 2
assert all(g.my_commander == "" and g.opp_commander == "" for g in store.games()) # not a Brawl match
assert store.backfill_commanders(None) == 0
def test_schema_without_commander_columns_gets_them(tmp_path):
import sqlite3
path = tmp_path / "v1.db"
conn = sqlite3.connect(path)
conn.executescript("""CREATE TABLE games (match_id TEXT NOT NULL, game_number INTEGER NOT NULL,
started_ms INTEGER, ended_ms INTEGER, event_id TEXT, opponent TEXT, deck_name TEXT, on_play TEXT,
result TEXT, my_colors TEXT, opp_colors TEXT, turns INTEGER, best_of INTEGER,
PRIMARY KEY (match_id, game_number));
INSERT INTO games VALUES ('m1', 1, 1, 2, 'Ladder', 'Opp', 'Deck', 'me', 'Won', 'U', 'R', 5, 1);""")
conn.commit()
conn.close()
games = HistoryStore(path).games()
assert len(games) == 1 and games[0].deck_name == "Deck" and games[0].my_commander is None