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:
315
src/functional_tests/test_component_cards_tarot.py
Normal file
315
src/functional_tests/test_component_cards_tarot.py
Normal 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"), ""
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user