""" Functional tests: deck-to-seat contribution mechanics. Sprint 1 (DeckContributionTest): Confirming a role in ROLE SELECT assigns the gamer's equipped deck to the TableSeat. The Game Kit immediately reflects the in-use state: the deck card gains the game name in its tooltip and the micro-status reads "In-Use". Sprint 2 (DeckInUseGameKitTest): With a deck already assigned to an active seat the Game Kit DON button is btn-disabled and no DOFF toggle is shown (unlike the normal equipped state where DOFF is visible). Clicking the card shows a tooltip naming the game. A second deck that is NOT assigned to any active seat has normal DON/DOFF behaviour. """ import time from django.test import tag from selenium.webdriver.common.by import By from apps.applets.models import Applet from apps.epic.models import DeckVariant, GateSlot, Room, TableSeat from apps.lyric.models import User from functional_tests.base import FunctionalTest from functional_tests.test_room_role_select import _fill_room_via_orm from functional_tests.test_room_sig_select import _assign_all_roles FOUNDER_EMAIL = "founder@test.io" GAMER_EMAIL = "gamer@test.io" def _equip_earthman(user): earthman, _ = DeckVariant.objects.get_or_create( slug="earthman", defaults={"name": "Earthman", "card_count": 106, "is_default": True}, ) user.equipped_deck = earthman user.save(update_fields=["equipped_deck"]) # Signal may not have added this (earthman didn't exist when the user was created) user.unlocked_decks.add(earthman) return earthman def _room_at_role_select(name="Wildfire"): """Create a room filled to 6 and at ROLE_SELECT table status.""" founder, _ = User.objects.get_or_create(email=FOUNDER_EMAIL) room = Room.objects.create(name=name, owner=founder) emails = [FOUNDER_EMAIL, GAMER_EMAIL, "b@test.io", "c@test.io", "d@test.io", "e@test.io"] _fill_room_via_orm(room, emails) for slot in room.gate_slots.all(): if slot.gamer: _equip_earthman(slot.gamer) room.table_status = Room.ROLE_SELECT room.save() return room, founder # ── Sprint 1 ───────────────────────────────────────────────────────────────── class DeckContributionTest(FunctionalTest): """Sprint 1: role confirmation → TableSeat.deck_variant set; Game Kit shows In-Use.""" def setUp(self): super().setUp() self.browser.set_window_size(800, 1200) for slug, name, ctx in [ ("game-kit", "Game Kit", "gameboard"), ("gk-decks", "Card Decks","game-kit"), ]: Applet.objects.get_or_create(slug=slug, defaults={"name": name, "context": ctx}) def test_confirming_role_assigns_equipped_deck_to_seat(self): """After the gamer confirms a role, Game Kit shows the Earthman deck as 'In-Use' with the game name in the deck tooltip, and the micro-status changes from 'Equipped' to 'In-Use'. """ room, founder = _room_at_role_select() gamer = User.objects.get(email=GAMER_EMAIL) # Create TableSeats; pre-assign slot 1 (PC) so gamer (slot 2) is the active seat for slot in room.gate_slots.order_by("slot_number"): role = "PC" if slot.slot_number == 1 else None TableSeat.objects.create( room=room, gamer=slot.gamer, slot_number=slot.slot_number, role=role ) # Gamer logs in and navigates to the role-select room self.create_pre_authenticated_session(GAMER_EMAIL) self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/") # Gamer is slot 2 — card stack is eligible because slot 1/PC is pre-assigned self.wait_for( lambda: self.browser.find_element(By.CSS_SELECTOR, ".card-stack[data-state='eligible']") ).click() self.wait_for(lambda: self.browser.find_element(By.ID, "id_role_select")) self.browser.find_element(By.CSS_SELECTOR, "#id_role_select .card").click() self.confirm_guard() # Deck is now assigned to the seat in the DB self.wait_for(lambda: self.assertTrue( TableSeat.objects.filter( gamer=gamer, room=room, deck_variant=gamer.equipped_deck, ).exists(), "TableSeat.deck_variant was not set after role confirmation", )) # Navigate to Game Kit — earthman deck renders directly (in-use, no placeholder click needed) self.browser.get(self.live_server_url + "/gameboard/") earthman_card = self.wait_for( lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_earthman_deck") ) # Game name lives on data-in-use-room-name; the mini-portal renders it as # "In-Use: " on hover (Jasmine covers the JS truncation). self.assertEqual( room.name, earthman_card.get_attribute("data-in-use-room-name") ) # ── Sprint 2 ───────────────────────────────────────────────────────────────── class DeckInUseGameKitTest(FunctionalTest): """Sprint 2: DON disabled + no DOFF toggle for in-use deck; second deck unaffected.""" def setUp(self): super().setUp() self.browser.set_window_size(800, 1200) for slug, name, ctx in [ ("game-kit", "Game Kit", "gameboard"), ("gk-decks", "Card Decks","game-kit"), ]: Applet.objects.get_or_create(slug=slug, defaults={"name": name, "context": ctx}) def _setup_in_use_deck(self): """Create a seated gamer whose Earthman deck is already assigned to a seat.""" room, founder = _room_at_role_select() gamer = User.objects.get(email=GAMER_EMAIL) earthman = _equip_earthman(gamer) # Assign deck directly (Sprint 1 must be green first) seat = TableSeat.objects.create( gamer=gamer, room=room, slot_number=2, role="NC", deck_variant=earthman ) return gamer, earthman, room, seat def test_don_is_disabled_and_doff_absent_for_in_use_deck(self): """DON button carries btn-disabled; DOFF is not rendered at all (not just disabled).""" gamer, earthman, room, seat = self._setup_in_use_deck() self.create_pre_authenticated_session(GAMER_EMAIL) self.browser.get(self.live_server_url + "/gameboard/") self.wait_for( lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_earthman_deck") ).click() # DON is disabled don_btn = self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, f"#id_kit_earthman_deck .btn-equip" ) ) self.assertIn("btn-disabled", don_btn.get_attribute("class")) # DOFF is present but disabled (both buttons disabled for in-use deck) doff_btn = self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, f"#id_kit_earthman_deck .btn-unequip" ) ) self.assertIn("btn-disabled", doff_btn.get_attribute("class"), "DOFF should be present but disabled for an in-use deck") def test_in_use_deck_carries_game_name_for_mini_portal(self): """The in-use deck token exposes the room name via data-in-use-room-name so the mini-portal can render it as 'In-Use: ' on hover.""" gamer, earthman, room, seat = self._setup_in_use_deck() self.create_pre_authenticated_session(GAMER_EMAIL) self.browser.get(self.live_server_url + "/gameboard/") deck_el = self.wait_for( lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_earthman_deck") ) self.assertEqual(room.name, deck_el.get_attribute("data-in-use-room-name")) def test_non_contributing_deck_has_normal_don_doff(self): """A deck not assigned to any active seat shows the normal DON/DOFF apparatus.""" gamer, earthman, room, seat = self._setup_in_use_deck() # Unlock Fiorentine for the gamer so it appears in Game Kit fiorentine, _ = DeckVariant.objects.get_or_create( slug="fiorentine-minchiate", defaults={"name": "Fiorentine Minchiate", "card_count": 97}, ) gamer.unlocked_decks.add(fiorentine) self.create_pre_authenticated_session(GAMER_EMAIL) self.browser.get(self.live_server_url + "/gameboard/") self.wait_for( lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_fiorentine_deck") ).click() don_btn = self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, "#id_kit_fiorentine_deck .btn-equip" ) ) self.assertNotIn("btn-disabled", don_btn.get_attribute("class"))