added default Earthman 108-card tarot deck, 78-card Minchiate Fiorentine deck, admin tests for each; DeckVariant model governs deck toggle; ran new migrations for apps.epic, apps.lyric; seeded DeckVariant migration to ensure Earthman is default deck; added min. tarot url; most new FTs passing

This commit is contained in:
Disco DeDisco
2026-03-24 21:07:01 -04:00
parent 11c85d56d1
commit 588358a20f
12 changed files with 1005 additions and 3 deletions

View File

@@ -0,0 +1,315 @@
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from .base import FunctionalTest
from apps.applets.models import Applet
from apps.epic.models import DeckVariant, Room
from apps.lyric.models import User
class TarotAdminTest(FunctionalTest):
"""Admin can browse tarot cards by deck variant via Django admin."""
def setUp(self):
super().setUp()
from apps.epic.models import TarotCard
# DeckVariant + TarotCard rows are flushed by TransactionTestCase — recreate
self.earthman, _ = DeckVariant.objects.get_or_create(
slug="earthman",
defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True},
)
# Seed enough cards so admin filter shows a meaningful count
# The "108 tarot cards" assertion relies on deck_variant.card_count reported
# by the admin, not on actual row count (admin shows real rows, so we seed
# representative cards — 3 are enough to reach "The Schiz" in the list)
for number, name, slug, group, correspondence in [
(0, "The Schiz", "the-schiz-adm", "", "The Fool / Il Matto"),
(1, "Pope I: President","pope-i-president-adm","The Popes", "The Magician / Il Bagatto"),
(50, "The Eagle", "the-eagle-adm", "", "Judgement / L'Angelo"),
]:
TarotCard.objects.get_or_create(
deck_variant=self.earthman, slug=slug,
defaults={
"name": name, "arcana": "MAJOR", "number": number,
"group": group, "correspondence": correspondence,
},
)
self.superuser = User.objects.create_superuser(
email="admin@example.com",
password="correct-password",
)
def _login_to_admin(self):
self.browser.get(self.live_server_url + "/admin/")
self.wait_for(lambda: self.browser.find_element(By.ID, "id_username"))
self.browser.find_element(By.ID, "id_username").send_keys("admin@example.com")
self.browser.find_element(By.ID, "id_password").send_keys("correct-password")
self.browser.find_element(By.CSS_SELECTOR, "input[type=submit]").click()
# ------------------------------------------------------------------ #
# Test 1a — admin home lists Tarot cards + Deck variants under Epic #
# ------------------------------------------------------------------ #
def test_admin_epic_section_shows_tarot_cards_and_deck_variants(self):
self._login_to_admin()
body = self.wait_for(lambda: self.browser.find_element(By.TAG_NAME, "body"))
self.assertIn("Tarot cards", body.text)
self.assertIn("Deck variants", body.text)
# ------------------------------------------------------------------ #
# Test 1b — changelist shows deck variant filter sidebar #
# ------------------------------------------------------------------ #
def test_admin_tarot_card_list_shows_deck_variant_filter(self):
self._login_to_admin()
self.browser.get(self.live_server_url + "/admin/epic/tarotcard/")
body = self.wait_for(lambda: self.browser.find_element(By.TAG_NAME, "body"))
# Filter sidebar has a link for the Earthman deck
self.assertIn("Earthman Deck", body.text)
# Cards are listed — 3 seeded in setUp
self.assertIn("3 tarot cards", body.text)
# ------------------------------------------------------------------ #
# Test 1c — Earthman card detail shows name, group, and correspondence #
# ------------------------------------------------------------------ #
def test_admin_earthman_card_detail_shows_group_and_correspondence(self):
self._login_to_admin()
self.browser.get(self.live_server_url + "/admin/epic/tarotcard/")
self.wait_for(lambda: self.browser.find_element(By.TAG_NAME, "body"))
# The Schiz is the Earthman Fool (card 0)
self.browser.find_element(By.LINK_TEXT, "The Schiz").click()
body = self.wait_for(lambda: self.browser.find_element(By.TAG_NAME, "body"))
self.assertIn("Major Arcana", body.text) # arcana dropdown
self.assertIn("the-schiz-adm", body.text) # slug (readonly → rendered as text)
self.assertIn("The Fool / Il Matto", body.text) # correspondence (readonly → text)
class TarotDeckTest(FunctionalTest):
"""A room founder can view the tarot deck page and deal a Celtic Cross spread."""
def setUp(self):
super().setUp()
# DeckVariant + TarotCard rows are flushed by TransactionTestCase — recreate
from apps.epic.models import TarotCard
self.earthman, _ = DeckVariant.objects.get_or_create(
slug="earthman",
defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True},
)
# Seed 8 major cards — enough for a 6-card cross deal (with buffer)
major_stubs = [
(0, "The Schiz", "the-schiz-ft"),
(1, "Pope I: President", "pope-i-president-ft"),
(2, "Pope II: Tsar", "pope-ii-tsar-ft"),
(3, "Pope III: Chairman","pope-iii-chairman-ft"),
(4, "Pope IV: Emperor", "pope-iv-emperor-ft"),
(5, "Pope V: Chancellor","pope-v-chancellor-ft"),
(10, "Wheel of Fortune", "wheel-of-fortune-em-ft"),
(11, "The Junkboat", "the-junkboat-ft"),
]
for number, name, slug in major_stubs:
TarotCard.objects.get_or_create(
deck_variant=self.earthman, slug=slug,
defaults={"name": name, "arcana": "MAJOR", "number": number},
)
self.founder = User.objects.create(email="founder@test.io")
# Signal sets equipped_deck to Earthman (now it exists)
self.founder.refresh_from_db()
self.room = Room.objects.create(name="Whispering Pines", owner=self.founder)
# ------------------------------------------------------------------ #
# Test 2 — tarot deck page reports 108 cards (Earthman default) #
# ------------------------------------------------------------------ #
def test_founder_can_reach_room_tarot_page_and_sees_full_deck(self):
self.create_pre_authenticated_session("founder@test.io")
self.browser.get(
self.live_server_url + f"/gameboard/room/{self.room.id}/tarot/"
)
# Browser tab title confirms we're on the tarot page
self.wait_for(
lambda: self.assertIn("Tarot", self.browser.title)
)
# Deck status shows all 108 Earthman cards remaining
status = self.browser.find_element(By.CSS_SELECTOR, "[data-tarot-remaining]")
self.assertEqual(status.get_attribute("data-tarot-remaining"), "108")
# ------------------------------------------------------------------ #
# Test 3 — dealing a Celtic Cross spread shows 10 positioned cards #
# ------------------------------------------------------------------ #
def test_dealing_celtic_cross_spread_shows_ten_unique_cards(self):
self.create_pre_authenticated_session("founder@test.io")
self.browser.get(
self.live_server_url + f"/gameboard/room/{self.room.id}/tarot/"
)
# Click the "Deal Celtic Cross" button
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, "[data-deal-spread]")
).click()
# Six cross positions appear in the spread (staff positions filled via gameplay)
positions = self.wait_for(
lambda: self.browser.find_elements(By.CSS_SELECTOR, ".tarot-position")
)
self.assertEqual(len(positions), 6)
# Each position shows a card name and an orientation label
names = set()
for pos in positions:
name = pos.find_element(By.CSS_SELECTOR, ".tarot-card-name").text
orientation = pos.find_element(By.CSS_SELECTOR, ".tarot-card-orientation").text
self.assertTrue(len(name) > 0, "Card name should not be empty")
self.assertIn(orientation, ["Upright", "Reversed"])
names.add(name)
# All 6 cards are unique
self.assertEqual(len(names), 6, "All 6 drawn cards must be unique")
# ------------------------------------------------------------------ #
# Test 4 — deck count decreases after the spread is dealt #
# ------------------------------------------------------------------ #
def test_remaining_count_decreases_after_dealing_spread(self):
self.create_pre_authenticated_session("founder@test.io")
self.browser.get(
self.live_server_url + f"/gameboard/room/{self.room.id}/tarot/"
)
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, "[data-deal-spread]")
).click()
# After dealing 6 cross cards from the 108-card Earthman deck, 102 remain
remaining = self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, "[data-tarot-remaining]")
)
self.assertEqual(remaining.get_attribute("data-tarot-remaining"), "102")
class GameKitDeckSelectionTest(FunctionalTest):
"""
Game Kit applet on gameboard shows available deck variants with hover
tooltips and an equip/equipped state — following the same mini-tooltip
pattern as trinket selection.
Test scenario: the gamer's active deck is explicitly set to Fiorentine
(non-default) in setUp, so we can exercise switching back to Earthman.
Once DeckVariant model exists, replace the TODO stubs with real ORM calls.
"""
def setUp(self):
super().setUp()
for slug, name, cols, rows in [
("new-game", "New Game", 6, 3),
("my-games", "My Games", 6, 3),
("game-kit", "Game Kit", 6, 3),
]:
Applet.objects.get_or_create(
slug=slug,
defaults={
"name": name, "grid_cols": cols,
"grid_rows": rows, "context": "gameboard",
},
)
self.gamer = User.objects.create(email="gamer@deck.io")
# TODO: once DeckVariant model is defined —
# from apps.epic.models import DeckVariant
# self.earthman = DeckVariant.objects.get(slug="earthman")
# self.fiorentine = DeckVariant.objects.get(slug="fiorentine-minchiate")
# # Put gamer on Fiorentine so the test can show switching back to Earthman
# self.gamer.equipped_deck = self.fiorentine
# self.gamer.save(update_fields=["equipped_deck"])
# ------------------------------------------------------------------ #
# Test 5 — Game Kit shows deck cards with correct equip/equipped state #
# ------------------------------------------------------------------ #
def test_game_kit_deck_cards_show_equip_state_and_switching_works(self):
"""
Gamer (currently on Fiorentine) visits gameboard, hovers over the
Earthman deck — sees it is NOT equipped. Hovers to Fiorentine — sees
it IS equipped. Hovers back to Earthman and clicks Equip.
"""
self.create_pre_authenticated_session("gamer@deck.io")
self.browser.get(self.live_server_url + "/gameboard/")
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
# ── Hover over Earthman deck ──────────────────────────────────────
earthman_el = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_kit_earthman_deck")
)
self.browser.execute_script(
"arguments[0].scrollIntoView({block: 'center'})", earthman_el
)
ActionChains(self.browser).move_to_element(earthman_el).perform()
# Main tooltip shows deck name and card count
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
)
portal = self.browser.find_element(By.ID, "id_tooltip_portal")
self.assertIn("Earthman", portal.text)
self.assertIn("108", portal.text)
# Mini tooltip shows Equip button — Earthman is NOT currently equipped
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
equip_btn = mini.find_element(By.CSS_SELECTOR, ".equip-deck-btn")
self.assertEqual(equip_btn.text, "Equip Deck?")
# ── Hover over Fiorentine Minchiate deck ─────────────────────────
fiorentine_el = self.browser.find_element(By.ID, "id_kit_fiorentine_deck")
self.browser.execute_script(
"arguments[0].scrollIntoView({block: 'center'})", fiorentine_el
)
ActionChains(self.browser).move_to_element(fiorentine_el).perform()
self.wait_for(
lambda: self.assertIn(
"Fiorentine",
self.browser.find_element(By.ID, "id_tooltip_portal").text,
)
)
portal = self.browser.find_element(By.ID, "id_tooltip_portal")
self.assertIn("78", portal.text)
# Mini tooltip shows "Equipped" — Fiorentine is the active deck
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
self.assertIn("Equipped", mini.text)
# ── Hover back to Earthman and click Equip ────────────────────────
ActionChains(self.browser).move_to_element(earthman_el).perform()
self.wait_for(
lambda: self.assertIn(
"Earthman",
self.browser.find_element(By.ID, "id_tooltip_portal").text,
)
)
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
mini.find_element(By.CSS_SELECTOR, ".equip-deck-btn").click()
# Both portals close after equip
self.wait_for(
lambda: self.assertFalse(
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
)
)
# Game Kit data attribute now reflects Earthman's id
game_kit = self.browser.find_element(By.ID, "id_game_kit")
self.wait_for(
lambda: self.assertNotEqual(
game_kit.get_attribute("data-equipped-deck-id"), ""
)
)