from selenium.common.exceptions import NoSuchElementException 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 Token, User class GameboardNavigationTest(FunctionalTest): def setUp(self): super().setUp() Applet.objects.get_or_create(slug="new-game", defaults={"name": "New Game", "context": "gameboard"}) Applet.objects.get_or_create(slug="my-games", defaults={"name": "My Games", "context": "gameboard"}) Applet.objects.get_or_create(slug="game-kit", defaults={"name": "Game Kit", "context": "gameboard"}) def test_footer_links_to_gameboard(self): # 1. Log in, nav to dashboard self.create_pre_authenticated_session("capman@test.io") self.browser.get(self.live_server_url) # 2. Assert footer nav present w. dash- & gameboard tabs self.browser.find_element(By.ID, "id_footer_nav") self.browser.find_element(By.CSS_SELECTOR, '#id_footer_nav a[href="/"]') self.browser.find_element(By.CSS_SELECTOR, '#id_footer_nav a[href="/gameboard/"]') # 3. Click the gameboard tab self.browser.find_element( By.CSS_SELECTOR, '#id_footer_nav a[href="/gameboard/"]' ).click() # 4. Assert user landed on gameboard self.wait_for( lambda: self.assertRegex(self.browser.current_url, r"/gameboard/$") ) def test_gameboard_shows_game_applets(self): # 1. Log in, nav directly to gameboard self.create_pre_authenticated_session("capman@test.io") self.browser.get(self.live_server_url + "/gameboard/") # 2. Assert My Games applet present self.wait_for( lambda: self.browser.find_element(By.ID, "id_applet_my_games") ) # 3. Assert no games listed yet for new user my_games = self.browser.find_element(By.ID, "id_applet_my_games") game_items = my_games.find_elements(By.CSS_SELECTOR, ".game-item") self.assertEqual(len(game_items), 0) # 4. Assert New Game applet present self.browser.find_element(By.ID, "id_applet_new_game") def test_game_kit_panel_shows_token_inventory(self): # 1. Log in, nav to gameboard self.create_pre_authenticated_session("capman@test.io") self.browser.get(self.live_server_url + "/gameboard/") # 2. Assert game kit applet & gear btn present self.wait_for( lambda: self.browser.find_element(By.ID, "id_game_kit") ) self.browser.find_element(By.CSS_SELECTOR, ".gear-btn") # 3. Assert Coin-on-a-String present in kit coin = self.browser.find_element(By.ID, "id_kit_coin_on_a_string") self.browser.execute_script("arguments[0].scrollIntoView({block: 'center'});", coin) # 6. Hover over it; assert tooltip shows name, entry text & reuse description ActionChains(self.browser).move_to_element(coin).perform() self.wait_for( lambda: self.assertTrue( self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed() ) ) coin_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text self.assertIn("Coin-on-a-String", coin_tooltip) self.assertIn("Admit 1 Entry", coin_tooltip) self.assertIn("and another after that", coin_tooltip) # 7. Assert 1× Free Token (complimentary) present in kit free_token = self.browser.find_element(By.ID, "id_kit_free_token") # 8. Hover over it; assert tooltip shows name, entry text & expiry date ActionChains(self.browser).move_to_element(free_token).perform() self.wait_for( lambda: self.assertTrue( self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed() ) ) free_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text self.assertIn("Free Token", free_tooltip) self.assertIn("Admit 1 Entry", free_tooltip) self.assertIn("Expires", free_tooltip) # 9. Assert card deck & dice set placeholder present self.browser.find_element(By.ID, "id_kit_card_deck") self.browser.find_element(By.ID, "id_kit_dice_set") class GameboardAppletMenuTest(FunctionalTest): def setUp(self): super().setUp() Applet.objects.get_or_create(slug="new-game", defaults={"name": "New Game", "context": "gameboard"}) Applet.objects.get_or_create(slug="my-games", defaults={"name": "My Games", "context": "gameboard"}) self.create_pre_authenticated_session("gamer@test.io") self.browser.get(self.live_server_url + "/gameboard/") def test_user_can_toggle_applet_visibility_via_gear_menu(self): # 1. Assert both applets present self.wait_for( lambda: self.browser.find_element(By.ID, "id_applet_my_games") ) self.browser.find_element(By.ID, "id_applet_new_game") # 2. Click gear; wait for menu self.browser.find_element(By.CSS_SELECTOR, ".gear-btn").click() self.wait_for( lambda: self.assertTrue( self.browser.find_element(By.ID, "id_game_applet_menu").is_displayed() ) ) # 3. Find checkboxes; assert both checked menu = self.browser.find_element(By.ID, "id_game_applet_menu") my_games_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="my-games"]') new_game_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="new-game"]') self.assertTrue(my_games_cb.is_selected()) self.assertTrue(new_game_cb.is_selected()) # 4. Uncheck my-games; plant no-reload marker; submit my_games_cb.click() self.assertFalse(my_games_cb.is_selected()) self.browser.execute_script("window.__no_reload_marker = true") menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click() # 5. Wait for menu to close; assert my-games gone, new game remains self.wait_for( lambda: self.assertFalse( self.browser.find_element(By.ID, "id_game_applet_menu").is_displayed() ) ) self.wait_for( lambda: self.assertRaises( NoSuchElementException, self.browser.find_element, By.ID, "id_applet_my_games", ) ) self.browser.find_element(By.ID, "id_applet_new_game") # 6. Re-check my-games; assert it reappears self.browser.find_element(By.CSS_SELECTOR, ".gear-btn").click() self.wait_for( lambda: self.assertTrue( self.browser.find_element(By.ID, "id_game_applet_menu").is_displayed() ) ) menu = self.browser.find_element(By.ID, "id_game_applet_menu") my_games_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="my-games"]') self.assertFalse(my_games_cb.is_selected()) my_games_cb.click() menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click() self.wait_for( lambda: self.assertFalse( self.browser.find_element(By.ID, "id_game_applet_menu").is_displayed() ) ) self.wait_for( lambda: self.assertTrue( self.browser.find_element(By.ID, "id_applet_my_games") ) ) # 7. Assert no full page reload occurred self.assertTrue(self.browser.execute_script("return window.__no_reload_marker === true")) class GameKitEquipTest(FunctionalTest): """DON|DOFF equip buttons in the game kit applet portal tooltip.""" def setUp(self): super().setUp() self.earthman, _ = DeckVariant.objects.get_or_create( slug="earthman", defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True}, ) Applet.objects.get_or_create(slug="game-kit", defaults={"name": "Game Kit", "context": "gameboard"}) self.create_pre_authenticated_session("gamer@equip.io") self.gamer = User.objects.get(email="gamer@equip.io") # Promote to staff so the pass_token appears in the game kit applet. self.gamer.is_staff = True self.gamer.save(update_fields=["is_staff"]) self.gamer.unlocked_decks.add(self.earthman) self.coin = self.gamer.tokens.filter(token_type=Token.COIN).first() # Create a PASS token manually — starts unequipped (coin is auto-equipped). self.pass_token = Token.objects.create(user=self.gamer, token_type=Token.PASS) self.browser.set_window_size(1200, 900) self.browser.get(self.live_server_url + "/gameboard/") def _hover_game_kit_token(self, token_el): """Hover token, wait for portal, return portal element.""" ActionChains(self.browser).move_to_element(token_el).perform() self.wait_for( lambda: self.assertTrue( self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed() ) ) return self.browser.find_element(By.ID, "id_tooltip_portal") def test_unequipped_token_shows_don_active_doff_disabled(self): """Backstage Pass — naturally unequipped (coin is auto-equipped): DON active, DOFF is ×.""" pass_el = self.wait_for( lambda: self.browser.find_element(By.ID, "id_kit_pass") ) portal = self._hover_game_kit_token(pass_el) don = portal.find_element(By.CSS_SELECTOR, ".btn-equip") doff = portal.find_element(By.CSS_SELECTOR, ".btn-unequip") self.assertNotIn("btn-disabled", don.get_attribute("class")) self.assertIn("btn-disabled", doff.get_attribute("class")) self.assertEqual(doff.text, "×") def test_equipped_token_shows_doff_active_don_disabled(self): """Auto-equipped coin: DOFF active, DON is ×; mini-portal says Equipped.""" coin = self.wait_for( lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string") ) portal = self._hover_game_kit_token(coin) don = portal.find_element(By.CSS_SELECTOR, ".btn-equip") doff = portal.find_element(By.CSS_SELECTOR, ".btn-unequip") self.assertIn("btn-disabled", don.get_attribute("class")) self.assertEqual(don.text, "×") self.assertNotIn("btn-disabled", doff.get_attribute("class")) mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal") self.assertEqual(mini.text, "Equipped") self.assertEqual(len(mini.find_elements(By.CSS_SELECTOR, "button")), 0) def test_doff_then_don_roundtrip(self): """Full roundtrip: DOFF unequips (portal stays open, buttons swap, mini updates); DON re-equips (buttons swap back, mini updates back, DB confirms).""" coin = self.wait_for( lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string") ) portal = self._hover_game_kit_token(coin) # — DOFF — portal.find_element(By.CSS_SELECTOR, ".btn-unequip").click() self.wait_for( lambda: self.assertIn( "btn-disabled", portal.find_element(By.CSS_SELECTOR, ".btn-unequip").get_attribute("class"), ) ) self.assertEqual(portal.find_element(By.CSS_SELECTOR, ".btn-unequip").text, "×") self.assertNotIn( "btn-disabled", portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"), ) self.assertEqual( self.browser.find_element(By.ID, "id_mini_tooltip_portal").text, "Not Equipped" ) self.gamer.refresh_from_db() self.assertIsNone(self.gamer.equipped_trinket) # — DON — portal.find_element(By.CSS_SELECTOR, ".btn-equip").click() self.wait_for( lambda: self.assertIn( "btn-disabled", portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"), ) ) self.assertEqual(portal.find_element(By.CSS_SELECTOR, ".btn-equip").text, "×") self.assertNotIn( "btn-disabled", portal.find_element(By.CSS_SELECTOR, ".btn-unequip").get_attribute("class"), ) self.assertEqual( self.browser.find_element(By.ID, "id_mini_tooltip_portal").text, "Equipped" ) self.gamer.refresh_from_db() self.assertEqual(self.gamer.equipped_trinket, self.coin) def test_doff_updates_open_kit_bag_dialog(self): """DOFF from game kit applet replaces trinket card in currently-open kit bag dialog.""" self.gamer.equipped_trinket = self.coin self.gamer.save(update_fields=["equipped_trinket"]) self.browser.refresh() # Open kit bag dialog self.browser.find_element(By.ID, "id_kit_btn").click() self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, f'#id_kit_bag_dialog [data-token-id="{self.coin.id}"]', ) ) # While dialog is open, hover coin and DOFF from game kit coin = self.wait_for( lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string") ) portal = self._hover_game_kit_token(coin) portal.find_element(By.CSS_SELECTOR, ".btn-unequip").click() # Dialog updates: trinket card replaced with placeholder self.wait_for( lambda: self.assertEqual( len(self.browser.find_elements( By.CSS_SELECTOR, f'#id_kit_bag_dialog [data-token-id="{self.coin.id}"]', )), 0 ) ) self.browser.find_element(By.CSS_SELECTOR, "#id_kit_bag_dialog .kit-bag-placeholder")