"""FTs for the Game Sign (a.k.a. My Significator) picker + billboard applet. Sprint 4a of [[project-my-sea-roadmap]]. The picker lives at `/billboard/my-sign/` — solo lift of the room's sig-select grid (no countdown / polarity / multi-user). Selection persists on User.significator + User.significator_reversed. "Significator" remains the storage-layer term + room sig-select context; this billboard surface is branded "Sign" / "Game Sign". """ from selenium.webdriver.common.by import By from .base import FunctionalTest from apps.applets.models import Applet from apps.epic.models import personal_sig_cards from apps.lyric.models import User def _seed_my_sign_applet(): Applet.objects.get_or_create( slug="my-sign", defaults={"name": "Game Sign", "context": "billboard", "default_visible": True, "grid_cols": 4, "grid_rows": 6}, ) class MySignPickerTest(FunctionalTest): """Happy-path picker: a user with the Earthman deck equipped lands at /billboard/my-sign/, picks a card, clicks SAVE SIGN, and sees the sig propagate to the Game Sign applet on /billboard/.""" # StaticLiveServerTestCase → TransactionTestCase flushes DB between tests, # wiping migration-seeded DeckVariant + TarotCard rows. Without this flag, # personal_sig_cards(user) returns [] because the signal that auto-equips # Earthman can't find the deck. See [[feedback_transactiontestcase_flush]]. serialized_rollback = True def setUp(self): super().setUp() _seed_my_sign_applet() # Seed the rest of the billboard applets so /billboard/ renders # without missing-applet errors. for slug, name in [ ("my-scrolls", "My Scrolls"), ("my-buds", "My Buds"), ("most-recent-scroll", "Most Recent Scroll"), ]: Applet.objects.get_or_create( slug=slug, defaults={"name": name, "context": "billboard"}, ) self.email = "sig@test.io" self.gamer = User.objects.create(email=self.email) # post_save signal auto-equips Earthman. Picker uses personal_sig_cards # (= 16 middle arcana + Major 0 & 1, filtered by Note unlocks) so the # target must come from that subset, not the full deck. sig_pile = personal_sig_cards(self.gamer) self.target_card = sig_pile[0] if sig_pile else None self.assertIsNotNone( self.target_card, "personal_sig_cards(user) returned no cards — check Earthman seed" " + DeckVariant fixture availability in the FT DB.", ) # ── Test 1 ─────────────────────────────────────────────────────────────── def test_picker_renders_card_grid_from_equipped_deck(self): """GET /billboard/my-sign/ → page renders w. a card grid + the page wordmark reads "Game Sign", populated by the user's equipped_deck.""" self.create_pre_authenticated_session(self.email) self.browser.get(self.live_server_url + "/billboard/my-sign/") # Wordmark. The h2 letter-splitter (base.html) wraps each character # in its own , so Selenium's `.text` joins them w. newlines — # strip whitespace before the substring check. self.wait_for( lambda: self.assertIn( "GAMESIGN", "".join( self.browser.find_element(By.CSS_SELECTOR, "h2").text.upper().split() ), ) ) # Target card present in the grid. The data-card-id selector itself # is the assertion — find_element raises if absent. Avoid asserting # against `.fan-corner-rank .text` (CSS hides it via font-size or # similar, so Selenium returns ""). self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, f'.my-sign-deck-grid .sig-card[data-card-id="{self.target_card.id}"]', ) ) # ── Test 2 ─────────────────────────────────────────────────────────────── def test_pick_card_then_save_persists_choice_and_shows_in_applet(self): """Click card → SAVE SIGN btn enables → click → DB updated → applet on /billboard/ shows the chosen card.""" self.create_pre_authenticated_session(self.email) self.browser.get(self.live_server_url + "/billboard/my-sign/") # Click target card card_el = self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, f'.my-sign-deck-grid .sig-card[data-card-id="{self.target_card.id}"]', ) ) self.browser.execute_script("arguments[0].click()", card_el) # SAVE SIGN should be enabled now save_btn = self.wait_for( lambda: self.browser.find_element(By.ID, "id_save_sign_btn") ) self.wait_for( lambda: self.assertFalse( save_btn.get_attribute("disabled"), "SAVE SIGN should be enabled after card click", ) ) # Hidden card_id input should match the clicked card self.assertEqual( str(self.target_card.id), self.browser.find_element(By.ID, "id_save_sign_card_id").get_attribute("value"), ) # Save → DB updated self.browser.execute_script("arguments[0].click()", save_btn) self.wait_for( lambda: self.gamer.refresh_from_db() or self.assertEqual( self.gamer.significator_id, self.target_card.id, ) ) # Navigate to /billboard/ → applet shows the saved card. Pin by # data-card-id (same reasoning as test 1 re: CSS-hidden corner rank). self.browser.get(self.live_server_url + "/billboard/") self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, f'#id_applet_my_sign .my-sign-applet-card[data-card-id="{self.target_card.id}"]', ) ) # ── Test 3 ─────────────────────────────────────────────────────────────── def test_applet_renders_blank_state_when_no_sig_chosen(self): """Fresh user with no significator → applet shows the empty-state copy, no card.""" self.create_pre_authenticated_session(self.email) self.browser.get(self.live_server_url + "/billboard/") empty = self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, "#id_applet_my_sign .my-sign-applet-empty" ) ) self.assertIn("No sign chosen", empty.text) self.assertEqual( len(self.browser.find_elements( By.CSS_SELECTOR, "#id_applet_my_sign .my-sign-applet-card" )), 0, )