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"), "" ) )